Compare commits

..

1 Commits

Author SHA1 Message Date
4a754166df Add menu action to download image and video on *ViewerActivity 2019-08-07 19:29:41 +02:00
113 changed files with 1207 additions and 2186 deletions

View File

@ -1,32 +1,8 @@
Changes in RiotX 0.4.0 (2019-XX-XX) Changes in RiotX 0.3.0 (2019-XX-XX)
===================================================
Features:
- Display read receipts in timeline (#81)
Improvements:
-
Other changes:
-
Bugfix:
- Fix text diff linebreak display (#441)
- Date change message repeats for each redaction until a normal message (#358)
- Slide-in reply icon is distorted (#423)
Translations:
-
Build:
-
Changes in RiotX 0.3.0 (2019-08-08)
=================================================== ===================================================
Features: Features:
- Create Direct Room flow - Create Direct Room flow
- Handle `/markdown` command
Improvements: Improvements:
- UI for pending edits (#193) - UI for pending edits (#193)
@ -35,10 +11,9 @@ Improvements:
- Enable proper cancellation of suspending functions (including db transaction) - Enable proper cancellation of suspending functions (including db transaction)
- Enhances network connectivity checks in SDK - Enhances network connectivity checks in SDK
- Add "View Edit History" item in the message bottom sheet (#401) - Add "View Edit History" item in the message bottom sheet (#401)
- Cancel sync request on pause and timeout to 0 after pause (#404)
Other changes: Other changes:
- Show sync progress also in room detail screen (#403) -
Bugfix: Bugfix:
- Edited message: link confusion when (edited) appears in body (#398) - Edited message: link confusion when (edited) appears in body (#398)
@ -48,6 +23,9 @@ Bugfix:
- Fix clear cache (#408) and Logout (#205) - Fix clear cache (#408) and Logout (#205)
- Fix `(edited)` link can be copied to clipboard (#402) - Fix `(edited)` link can be copied to clipboard (#402)
Translations:
-
Build: Build:
- Split APK: generate one APK per arch, to reduce APK size of about 30% - Split APK: generate one APK per arch, to reduce APK size of about 30%

View File

@ -18,7 +18,6 @@ package im.vector.matrix.rx
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
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.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.reactivex.Observable import io.reactivex.Observable
@ -50,10 +49,6 @@ class RxRoom(private val room: Room) {
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it) room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
} }
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
return room.getEventReadReceiptsLive(eventId).asObservable()
}
} }
fun Room.rx(): RxRoom { fun Room.rx(): RxRoom {

View File

@ -16,9 +16,8 @@
package im.vector.matrix.android.api.session.room.model package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.user.model.User
data class ReadReceipt( data class ReadReceipt(
val user: User, val userId: String,
val eventId: String,
val originServerTs: Long val originServerTs: Long
) )

View File

@ -16,9 +16,7 @@
package im.vector.matrix.android.api.session.room.read package im.vector.matrix.android.api.session.room.read
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.model.ReadReceipt
/** /**
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level. * This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
@ -41,6 +39,4 @@ interface ReadService {
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
fun isEventRead(eventId: String): Boolean fun isEventRead(eventId: String): Boolean
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
} }

View File

@ -20,7 +20,6 @@ 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.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
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.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
@ -38,8 +37,7 @@ data class TimelineEvent(
val senderName: String?, val senderName: String?,
val isUniqueDisplayName: Boolean, val isUniqueDisplayName: Boolean,
val senderAvatar: String?, val senderAvatar: String?,
val annotations: EventAnnotationsSummary? = null, val annotations: EventAnnotationsSummary? = null
val readReceipts: List<ReadReceipt> = emptyList()
) { ) {
val metadata = HashMap<String, Any>() val metadata = HashMap<String, Any>()
@ -67,8 +65,8 @@ data class TimelineEvent(
"$name (${root.senderId})" "$name (${root.senderId})"
} }
} }
?: root.senderId ?: root.senderId
?: "" ?: ""
} }
/** /**
@ -96,7 +94,7 @@ fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
* Get last MessageContent, after a possible edition * Get last MessageContent, after a possible edition
*/ */
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel() fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel() ?: root.getClearContent().toModel()
fun TimelineEvent.getTextEditableContent(): String? { fun TimelineEvent.getTextEditableContent(): String? {

View File

@ -25,12 +25,12 @@ interface TimelineService {
/** /**
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink. * Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
* You can also configure some settings with the [settings] param. * You can filter the type you want to grab with the allowedTypes param.
* @param eventId the optional initial eventId. * @param eventId the optional initial eventId.
* @param settings settings to configure the timeline. * @param allowedTypes the optional filter types
* @return the instantiated timeline * @return the instantiated timeline
*/ */
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline fun createTimeline(eventId: String?, allowedTypes: List<String>? = null): Timeline
fun getTimeLineEvent(eventId: String): TimelineEvent? fun getTimeLineEvent(eventId: String): TimelineEvent?

View File

@ -1,44 +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.matrix.android.api.session.room.timeline
/**
* Data class holding setting values for a [Timeline] instance.
*/
data class TimelineSettings(
/**
* The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet.
*/
val initialSize: Int,
/**
* A flag to filter edit events
*/
val filterEdits: Boolean = false,
/**
* A flag to filter by types. It should be used with [allowedTypes] field
*/
val filterTypes: Boolean = false,
/**
* If [filterTypes] is true, the list of types allowed by the list.
*/
val allowedTypes: List<String> = emptyList(),
/**
* If true, will build read receipts for each event.
*/
val buildReadReceipts: Boolean = true
)

View File

@ -23,12 +23,9 @@ import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.toEntity import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.extensions.assertIsManaged import im.vector.matrix.android.internal.extensions.assertIsManaged
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
@ -136,28 +133,6 @@ internal fun ChunkEntity.add(roomId: String,
} }
val localId = TimelineEventEntity.nextId(realm) val localId = TimelineEventEntity.nextId(realm)
val eventId = event.eventId ?: ""
val senderId = event.senderId ?: ""
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
?: ReadReceiptsSummaryEntity(eventId, roomId)
// Update RR for the sender of a new message with a dummy one
if (event.originServerTs != null) {
val timestampOfEvent = event.originServerTs.toDouble()
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
// If the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
readReceiptOfSender.eventId = eventId
readReceiptOfSender.originServerTs = timestampOfEvent
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
}
}
val eventEntity = TimelineEventEntity(localId).also { val eventEntity = TimelineEventEntity(localId).also {
it.root = event.toEntity(roomId).apply { it.root = event.toEntity(roomId).apply {
this.stateIndex = currentStateIndex this.stateIndex = currentStateIndex
@ -165,10 +140,9 @@ internal fun ChunkEntity.add(roomId: String,
this.displayIndex = currentDisplayIndex this.displayIndex = currentDisplayIndex
this.sendState = SendState.SYNCED this.sendState = SendState.SYNCED
} }
it.eventId = eventId it.eventId = event.eventId ?: ""
it.roomId = roomId it.roomId = roomId
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() it.annotations = EventAnnotationsSummaryEntity.where(realm, it.eventId).findFirst()
it.readReceipts = readReceiptsSummaryEntity
} }
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
timelineEvents.add(position, eventEntity) timelineEvents.add(position, eventEntity)
@ -176,14 +150,14 @@ internal fun ChunkEntity.add(roomId: String,
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> forwardsDisplayIndex PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue } ?: defaultValue
} }
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> forwardsStateIndex PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue } ?: defaultValue
} }

View File

@ -1,45 +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.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.SessionDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> {
if (readReceiptsSummaryEntity == null) {
return emptyList()
}
return Realm.getInstance(realmConfiguration).use { realm ->
val readReceipts = readReceiptsSummaryEntity.readReceipts
readReceipts
.mapNotNull {
val user = UserEntity.where(realm, it.userId).findFirst()
?: return@mapNotNull null
ReadReceipt(user.asDomain(), it.originServerTs.toLong())
}
}
}
}

View File

@ -26,8 +26,7 @@ import java.util.*
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor( internal class RoomSummaryMapper @Inject constructor(
val cryptoService: CryptoService, val cryptoService: CryptoService
val timelineEventMapper: TimelineEventMapper
) { ) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
@ -35,9 +34,7 @@ internal class RoomSummaryMapper @Inject constructor(
RoomTag(it.tagName, it.tagOrder) RoomTag(it.tagName, it.tagOrder)
} }
val latestEvent = roomSummaryEntity.latestEvent?.let { val latestEvent = roomSummaryEntity.latestEvent?.asDomain()
timelineEventMapper.map(it)
}
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) { if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
//TODO use a global event decryptor? attache to session and that listen to new sessionId? //TODO use a global event decryptor? attache to session and that listen to new sessionId?
//for now decrypt sync //for now decrypt sync

View File

@ -17,38 +17,29 @@
package im.vector.matrix.android.internal.database.mapper package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.ReadReceipt
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.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import javax.inject.Inject
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) { internal object TimelineEventMapper {
fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
val readReceipts = if (buildReadReceipts) {
correctedReadReceipts ?: timelineEventEntity.readReceipts
?.let {
readReceiptsSummaryMapper.map(it)
}
} else {
null
}
return TimelineEvent( return TimelineEvent(
root = timelineEventEntity.root?.asDomain() root = timelineEventEntity.root?.asDomain()
?: Event("", timelineEventEntity.eventId), ?: Event("", timelineEventEntity.eventId),
annotations = timelineEventEntity.annotations?.asDomain(), annotations = timelineEventEntity.annotations?.asDomain(),
localId = timelineEventEntity.localId, localId = timelineEventEntity.localId,
displayIndex = timelineEventEntity.root?.displayIndex ?: 0, displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
senderName = timelineEventEntity.senderName, senderName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
senderAvatar = timelineEventEntity.senderAvatar, senderAvatar = timelineEventEntity.senderAvatar
readReceipts = readReceipts?.sortedByDescending {
it.originServerTs
} ?: emptyList()
) )
} }
} }
internal fun TimelineEventEntity.asDomain(): TimelineEvent {
return TimelineEventMapper.map(this)
}

View File

@ -17,18 +17,13 @@
package im.vector.matrix.android.internal.database.model package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
internal open class ReadReceiptEntity(@PrimaryKey var primaryKey: String = "", internal open class ReadReceiptEntity(@PrimaryKey var primaryKey: String = "",
var eventId: String = "", var userId: String = "",
var roomId: String = "", var eventId: String = "",
var userId: String = "", var roomId: String = "",
var originServerTs: Double = 0.0 var originServerTs: Double = 0.0
) : RealmObject() { ) : RealmObject() {
companion object companion object
@LinkingObjects("readReceipts")
val summary: RealmResults<ReadReceiptsSummaryEntity>? = null
} }

View File

@ -1,37 +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.matrix.android.internal.database.model
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
internal open class ReadReceiptsSummaryEntity(
@PrimaryKey
var eventId: String = "",
var roomId: String = "",
var readReceipts: RealmList<ReadReceiptEntity> = RealmList()
) : RealmObject() {
@LinkingObjects("readReceipts")
val timelineEvent: RealmResults<TimelineEventEntity>? = null
companion object
}

View File

@ -22,27 +22,26 @@ import io.realm.annotations.RealmModule
* Realm module for Session * Realm module for Session
*/ */
@RealmModule(library = true, @RealmModule(library = true,
classes = [ classes = [
ChunkEntity::class, ChunkEntity::class,
EventEntity::class, EventEntity::class,
TimelineEventEntity::class, TimelineEventEntity::class,
FilterEntity::class, FilterEntity::class,
GroupEntity::class, GroupEntity::class,
GroupSummaryEntity::class, GroupSummaryEntity::class,
ReadReceiptEntity::class, ReadReceiptEntity::class,
RoomEntity::class, RoomEntity::class,
RoomSummaryEntity::class, RoomSummaryEntity::class,
RoomTagEntity::class, RoomTagEntity::class,
SyncEntity::class, SyncEntity::class,
UserEntity::class, UserEntity::class,
EventAnnotationsSummaryEntity::class, EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class, ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class,
PushRulesEntity::class, PushRulesEntity::class,
PushRuleEntity::class, PushRuleEntity::class,
PushConditionEntity::class, PushConditionEntity::class,
PusherEntity::class, PusherEntity::class,
PusherDataEntity::class, PusherDataEntity::class
ReadReceiptsSummaryEntity::class ])
])
internal class SessionRealmModule internal class SessionRealmModule

View File

@ -30,8 +30,7 @@ internal open class TimelineEventEntity(var localId: Long = 0,
var senderName: String? = null, var senderName: String? = null,
var isUniqueDisplayName: Boolean = false, var isUniqueDisplayName: Boolean = false,
var senderAvatar: String? = null, var senderAvatar: String? = null,
var senderMembershipEvent: EventEntity? = null, var senderMembershipEvent: EventEntity? = null
var readReceipts: ReadReceiptsSummaryEntity? = null
) : RealmObject() { ) : RealmObject() {
@LinkingObjects("timelineEvents") @LinkingObjects("timelineEvents")

View File

@ -1,23 +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.matrix.android.internal.database.query
internal object FilterContent {
internal const val EDIT_TYPE = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
}

View File

@ -27,21 +27,3 @@ internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, use
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId) .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
.equalTo(ReadReceiptEntityFields.USER_ID, userId) .equalTo(ReadReceiptEntityFields.USER_ID, userId)
} }
internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
return ReadReceiptEntity().apply {
this.primaryKey = "${roomId}_$userId"
this.eventId = eventId
this.roomId = roomId
this.userId = userId
this.originServerTs = originServerTs
}
}
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
?: realm.createObject(ReadReceiptEntity::class.java, "${roomId}_$userId").apply {
this.roomId = roomId
this.userId = userId
}
}

View File

@ -1,36 +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.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
internal fun ReadReceiptsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<ReadReceiptsSummaryEntity> {
return realm.where<ReadReceiptsSummaryEntity>()
.equalTo(ReadReceiptsSummaryEntityFields.EVENT_ID, eventId)
}
internal fun ReadReceiptsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery<ReadReceiptsSummaryEntity> {
val query = realm.where<ReadReceiptsSummaryEntity>()
if (roomId != null) {
query.equalTo(ReadReceiptsSummaryEntityFields.ROOM_ID, roomId)
}
return query
}

View File

@ -1,24 +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.matrix.android.internal.di
import com.squareup.inject.assisted.dagger2.AssistedModule
import dagger.Module
@AssistedModule
@Module(includes = [AssistedInject_SessionAssistedInjectModule::class])
interface SessionAssistedInjectModule

View File

@ -22,7 +22,6 @@ import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.crypto.CryptoModule import im.vector.matrix.android.internal.crypto.CryptoModule
import im.vector.matrix.android.internal.di.MatrixComponent import im.vector.matrix.android.internal.di.MatrixComponent
import im.vector.matrix.android.internal.di.SessionAssistedInjectModule
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.cache.CacheModule import im.vector.matrix.android.internal.session.cache.CacheModule
import im.vector.matrix.android.internal.session.content.ContentModule import im.vector.matrix.android.internal.session.content.ContentModule
@ -60,8 +59,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
CacheModule::class, CacheModule::class,
CryptoModule::class, CryptoModule::class,
PushersModule::class, PushersModule::class,
AccountDataModule::class, AccountDataModule::class
SessionAssistedInjectModule::class
] ]
) )
@SessionScope @SessionScope

View File

@ -36,9 +36,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.DefaultRoomFactory
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
import im.vector.matrix.android.internal.session.room.RoomFactory
import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver
import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver
@ -116,6 +114,7 @@ internal abstract class SessionModule {
} }
} }
@Binds @Binds
abstract fun bindSession(session: DefaultSession): Session abstract fun bindSession(session: DefaultSession): Session

View File

@ -16,46 +16,70 @@
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import android.content.Context
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
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.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.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.state.DefaultStateService import im.vector.matrix.android.internal.session.room.state.DefaultStateService
import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.task.TaskExecutor
import javax.inject.Inject import javax.inject.Inject
internal class RoomFactory @Inject constructor(private val context: Context,
private val credentials: Credentials,
private val monarchy: Monarchy,
private val eventFactory: LocalEchoEventFactory,
private val roomSummaryMapper: RoomSummaryMapper,
private val taskExecutor: TaskExecutor,
private val loadRoomMembersTask: LoadRoomMembersTask,
private val inviteTask: InviteTask,
private val sendStateTask: SendStateTask,
private val paginationTask: PaginationTask,
private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask,
private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val fetchEditHistoryTask: FetchEditHistoryTask,
private val joinRoomTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask) {
internal interface RoomFactory { fun create(roomId: String): Room {
fun create(roomId: String): Room val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask)
} val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask)
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials)
val relationService = DefaultRelationService(context,
credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, fetchEditHistoryTask, monarchy, taskExecutor)
internal class DefaultRoomFactory @Inject constructor(private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val cryptoService: CryptoService,
private val timelineServiceFactory: DefaultTimelineService.Factory,
private val sendServiceFactory: DefaultSendService.Factory,
private val stateServiceFactory: DefaultStateService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory) :
RoomFactory {
override fun create(roomId: String): Room {
return DefaultRoom( return DefaultRoom(
roomId, roomId,
monarchy, monarchy,
roomSummaryMapper, roomSummaryMapper,
timelineServiceFactory.create(roomId), timelineService,
sendServiceFactory.create(roomId), sendService,
stateServiceFactory.create(roomId), stateService,
readServiceFactory.create(roomId), readService,
cryptoService, cryptoService,
relationServiceFactory.create(roomId), relationService,
membershipServiceFactory.create(roomId) roomMembersService
) )
} }

View File

@ -22,6 +22,12 @@ import dagger.Provides
import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.RoomDirectoryService import im.vector.matrix.android.api.session.room.RoomDirectoryService
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.session.DefaultFileService import im.vector.matrix.android.internal.session.DefaultFileService
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
@ -31,6 +37,7 @@ import im.vector.matrix.android.internal.session.room.directory.DefaultGetThirdP
import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask import im.vector.matrix.android.internal.session.room.directory.GetPublicRoomTask
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask
@ -40,20 +47,15 @@ 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.membership.leaving.LeaveRoomTask
import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask 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.prune.PruneEventTask
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
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.DefaultFetchEditHistoryTask import im.vector.matrix.android.internal.session.room.relation.*
import im.vector.matrix.android.internal.session.room.relation.DefaultFindReactionEventForUndoTask import im.vector.matrix.android.internal.session.room.send.DefaultSendService
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.state.DefaultSendStateTask import im.vector.matrix.android.internal.session.room.state.DefaultSendStateTask
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
import im.vector.matrix.android.internal.session.room.state.SendStateTask import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.*
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 import retrofit2.Retrofit
@Module @Module
@ -69,9 +71,6 @@ internal abstract class RoomModule {
} }
} }
@Binds
abstract fun bindRoomFactory(roomFactory: DefaultRoomFactory): RoomFactory
@Binds @Binds
abstract fun bindRoomService(roomService: DefaultRoomService): RoomService abstract fun bindRoomService(roomService: DefaultRoomService): RoomService
@ -99,15 +98,24 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask
@Binds
abstract fun bindMembershipService(membershipService: DefaultMembershipService): MembershipService
@Binds @Binds
abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask
@Binds @Binds
abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask
@Binds
abstract fun bindReadService(readService: DefaultReadService): ReadService
@Binds @Binds
abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask
@Binds
abstract fun bindRelationService(relationService: DefaultRelationService): RelationService
@Binds @Binds
abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
@ -117,12 +125,21 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask
@Binds
abstract fun bindSendService(sendService: DefaultSendService): SendService
@Binds
abstract fun bindStateService(stateService: DefaultStateService): StateService
@Binds @Binds
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
@Binds @Binds
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
@Binds
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
@Binds @Binds
abstract fun bindFileService(fileService: DefaultFileService): FileService abstract fun bindFileService(fileService: DefaultFileService): FileService

View File

@ -17,8 +17,6 @@
package im.vector.matrix.android.internal.session.room.membership package im.vector.matrix.android.internal.session.room.membership
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
@ -33,21 +31,17 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRo
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchCopied import im.vector.matrix.android.internal.util.fetchCopied
import javax.inject.Inject
internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultMembershipService @Inject constructor(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val inviteTask: InviteTask, private val inviteTask: InviteTask,
private val joinTask: JoinRoomTask, private val joinTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask private val leaveRoomTask: LeaveRoomTask
) : MembershipService { ) : MembershipService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): MembershipService
}
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable { override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
return loadRoomMembersTask return loadRoomMembersTask

View File

@ -16,38 +16,24 @@
package im.vector.matrix.android.internal.session.room.read package im.vector.matrix.android.internal.session.room.read
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.read.ReadService import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject
internal class DefaultReadService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultReadService @Inject constructor(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val setReadMarkersTask: SetReadMarkersTask, private val setReadMarkersTask: SetReadMarkersTask,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, private val credentials: Credentials) : ReadService {
private val credentials: Credentials
) : ReadService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): ReadService
}
override fun markAllAsRead(callback: MatrixCallback<Unit>) { override fun markAllAsRead(callback: MatrixCallback<Unit>) {
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true) val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
@ -81,28 +67,16 @@ internal class DefaultReadService @AssistedInject constructor(@Assisted private
var isEventRead = false var isEventRead = false
monarchy.doWithRealm { monarchy.doWithRealm {
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst() val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
?: return@doWithRealm ?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
?: return@doWithRealm ?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
?: Int.MIN_VALUE ?: Int.MIN_VALUE
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
?: Int.MAX_VALUE ?: Int.MAX_VALUE
isEventRead = eventToCheckIndex <= readReceiptIndex isEventRead = eventToCheckIndex <= readReceiptIndex
} }
return isEventRead return isEventRead
} }
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
ReadReceiptsSummaryEntity.where(realm, eventId)
}
return Transformations.map(liveEntity) { realmResults ->
realmResults.firstOrNull()?.let {
readReceiptsSummaryMapper.map(it)
}?.sortedByDescending {
it.originServerTs
} ?: emptyList()
}
}
} }

View File

@ -19,8 +19,6 @@ import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
@ -47,23 +45,19 @@ import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultRelationService @Inject constructor(private val context: Context,
private val context: Context, private val credentials: Credentials,
private val credentials: Credentials, private val roomId: String,
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 fetchEditHistoryTask: FetchEditHistoryTask,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor) private val taskExecutor: TaskExecutor)
: RelationService { : RelationService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): RelationService
}
override fun sendReaction(reaction: String, targetEventId: String): Cancelable { override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
.also { .also {
@ -154,9 +148,9 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
compatibilityBodyText: String): Cancelable { compatibilityBodyText: String): Cancelable {
val event = eventFactory val event = eventFactory
.createReplaceTextOfReply(roomId, .createReplaceTextOfReply(roomId,
replyToEdit, replyToEdit,
originalEvent, originalEvent,
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText) newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
.also { .also {
saveLocalEcho(it) saveLocalEcho(it)
} }
@ -220,7 +214,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
} }
return Transformations.map(liveEntity) { realmResults -> return Transformations.map(liveEntity) { realmResults ->
realmResults.firstOrNull()?.asDomain() realmResults.firstOrNull()?.asDomain()
?: EventAnnotationsSummary(eventId, emptyList(), null) ?: EventAnnotationsSummary(eventId, emptyList(), null)
} }
} }
@ -233,7 +227,7 @@ internal class DefaultRelationService @AssistedInject constructor(@Assisted priv
private fun saveLocalEcho(event: Event) { private fun saveLocalEcho(event: Event) {
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
?: return@writeAsync ?: return@writeAsync
roomEntity.addSendingEvent(event) roomEntity.addSendingEvent(event)
} }
} }

View File

@ -17,22 +17,12 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send
import android.content.Context import android.content.Context
import androidx.work.BackoffPolicy import androidx.work.*
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.Operation
import androidx.work.WorkManager
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.*
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.isImageMessage
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.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.send.SendService import im.vector.matrix.android.api.session.room.send.SendService
@ -57,22 +47,18 @@ import im.vector.matrix.android.internal.worker.startChain
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject
private const val UPLOAD_WORK = "UPLOAD_WORK" private const val UPLOAD_WORK = "UPLOAD_WORK"
private const val BACKOFF_DELAY = 10_000L private const val BACKOFF_DELAY = 10_000L
internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultSendService @Inject constructor(private val context: Context,
private val context: Context, private val credentials: Credentials,
private val credentials: Credentials, private val roomId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val monarchy: Monarchy private val monarchy: Monarchy)
) : SendService { : SendService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): SendService
}
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor() private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable { override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
@ -166,11 +152,11 @@ internal class DefaultSendService @AssistedInject constructor(@Assisted private
override fun deleteFailedEcho(localEcho: TimelineEvent) { override fun deleteFailedEcho(localEcho: TimelineEvent) {
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
TimelineEventEntity.where(realm, eventId = localEcho.root.eventId TimelineEventEntity.where(realm, eventId = localEcho.root.eventId
?: "").findFirst()?.let { ?: "").findFirst()?.let {
it.deleteFromRealm() it.deleteFromRealm()
} }
EventEntity.where(realm, eventId = localEcho.root.eventId EventEntity.where(realm, eventId = localEcho.root.eventId
?: "").findFirst()?.let { ?: "").findFirst()?.let {
it.deleteFromRealm() it.deleteFromRealm()
} }
} }

View File

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.session.room.state package im.vector.matrix.android.internal.session.room.state
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.MatrixCallback
import im.vector.matrix.android.api.session.events.model.Event 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.EventType
@ -31,18 +29,13 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import javax.inject.Inject
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultStateService @Inject constructor(private val roomId: String,
@SessionDatabase @SessionDatabase
private val realmConfiguration: RealmConfiguration, private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val sendStateTask: SendStateTask private val sendStateTask: SendStateTask) : StateService {
) : StateService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): StateService
}
override fun getStateEvent(eventType: String): Event? { override fun getStateEvent(eventType: String): Event? {
return Realm.getInstance(realmConfiguration).use { realm -> return Realm.getInstance(realmConfiguration).use { realm ->
@ -52,10 +45,10 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) { override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
val params = SendStateTask.Params(roomId, val params = SendStateTask.Params(roomId,
EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_TOPIC,
mapOf( mapOf(
"topic" to topic "topic" to topic
)) ))
sendStateTask sendStateTask

View File

@ -19,41 +19,21 @@ package im.vector.matrix.android.internal.session.room.timeline
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.timeline.Timeline 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.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.query.*
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.FilterContent
import im.vector.matrix.android.internal.database.query.findAllInRoomWithSendStates
import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.database.query.whereInRoom
import im.vector.matrix.android.internal.task.TaskConstraints import im.vector.matrix.android.internal.task.TaskConstraints
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.Debouncer import im.vector.matrix.android.internal.util.Debouncer
import im.vector.matrix.android.internal.util.createBackgroundHandler import im.vector.matrix.android.internal.util.createBackgroundHandler
import im.vector.matrix.android.internal.util.createUIHandler import im.vector.matrix.android.internal.util.createUIHandler
import io.realm.OrderedCollectionChangeSet import io.realm.*
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -62,6 +42,7 @@ import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
private const val INITIAL_LOAD_SIZE = 30
private const val MIN_FETCHING_COUNT = 30 private const val MIN_FETCHING_COUNT = 30
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
@ -72,11 +53,9 @@ internal class DefaultTimeline(
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
private val cryptoService: CryptoService, cryptoService: CryptoService,
private val timelineEventMapper: TimelineEventMapper, private val allowedTypes: List<String>?
private val settings: TimelineSettings, ) : Timeline {
private val hiddenReadReceipts: TimelineHiddenReadReceipts
) : Timeline, TimelineHiddenReadReceipts.Delegate {
private companion object { private companion object {
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD") val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
@ -98,8 +77,6 @@ internal class DefaultTimeline(
private val debouncer = Debouncer(mainHandler) private val debouncer = Debouncer(mainHandler)
private lateinit var liveEvents: RealmResults<TimelineEventEntity> private lateinit var liveEvents: RealmResults<TimelineEventEntity>
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
private var roomEntity: RoomEntity? = null private var roomEntity: RoomEntity? = null
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
@ -110,8 +87,11 @@ internal class DefaultTimeline(
private val backwardsPaginationState = AtomicReference(PaginationState()) private val backwardsPaginationState = AtomicReference(PaginationState())
private val forwardsPaginationState = AtomicReference(PaginationState()) private val forwardsPaginationState = AtomicReference(PaginationState())
private val timelineID = UUID.randomUUID().toString() private val timelineID = UUID.randomUUID().toString()
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService) private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet -> private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
@ -150,9 +130,9 @@ internal class DefaultTimeline(
val eventEntity = results[index] val eventEntity = results[index]
eventEntity?.eventId?.let { eventId -> eventEntity?.eventId?.let { eventId ->
builtEventsIdMap[eventId]?.let { builtIndex -> builtEventsIdMap[eventId]?.let { builtIndex ->
//Update an existing event //Update the relation of existing event
builtEvents[builtIndex]?.let { te -> builtEvents[builtIndex]?.let { te ->
builtEvents[builtIndex] = buildTimelineEvent(eventEntity) builtEvents[builtIndex] = eventEntity.asDomain()
hasChanged = true hasChanged = true
} }
} }
@ -182,8 +162,34 @@ internal class DefaultTimeline(
postSnapshot() postSnapshot()
} }
// private val newSessionListener = object : NewSessionListener {
// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
// if (roomId == this@DefaultTimeline.roomId) {
// Timber.v("New session id detected for this room")
// BACKGROUND_HANDLER.post {
// val realm = backgroundRealm.get()
// var hasChange = false
// builtEvents.forEachIndexed { index, timelineEvent ->
// if (timelineEvent.isEncrypted()) {
// val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
// if (eventContent?.sessionId == sessionId
// && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
// //we need to rebuild this event
// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
// //builtEvents[index] = timelineEventFactory.create(it, realm)
// hasChange = true
// }
// }
// }
// }
// if (hasChange) postSnapshot()
// }
// }
// }
//
// }
// Public methods ****************************************************************************** // Public methods ******************************************************************************
override fun paginate(direction: Timeline.Direction, count: Int) { override fun paginate(direction: Timeline.Direction, count: Int) {
BACKGROUND_HANDLER.post { BACKGROUND_HANDLER.post {
@ -228,20 +234,15 @@ internal class DefaultTimeline(
} }
liveEvents = buildEventQuery(realm) liveEvents = buildEventQuery(realm)
.filterEventsWithSettings()
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING) .sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
.findAllAsync() .findAllAsync()
.also { it.addChangeListener(eventsChangeListener) } .also { it.addChangeListener(eventsChangeListener) }
isReady.set(true)
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId) eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
.findAllAsync() .findAllAsync()
.also { it.addChangeListener(relationsListener) } .also { it.addChangeListener(relationsListener) }
if (settings.buildReadReceipts) {
hiddenReadReceipts.start(realm, liveEvents, this)
}
isReady.set(true)
} }
} }
} }
@ -255,9 +256,6 @@ internal class DefaultTimeline(
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners() roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
eventRelations.removeAllChangeListeners() eventRelations.removeAllChangeListeners()
liveEvents.removeAllChangeListeners() liveEvents.removeAllChangeListeners()
if (settings.buildReadReceipts) {
hiddenReadReceipts.dispose()
}
backgroundRealm.getAndSet(null).also { backgroundRealm.getAndSet(null).also {
it.close() it.close()
} }
@ -269,28 +267,12 @@ internal class DefaultTimeline(
return hasMoreInCache(direction) || !hasReachedEnd(direction) return hasMoreInCache(direction) || !hasReachedEnd(direction)
} }
// TimelineHiddenReadReceipts.Delegate
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
return builtEventsIdMap[eventId]?.let { builtIndex ->
//Update the relation of existing event
builtEvents[builtIndex]?.let { te ->
builtEvents[builtIndex] = te.copy(readReceipts = readReceipts)
true
}
} ?: false
}
override fun onReadReceiptsUpdated() {
postSnapshot()
}
// Private methods ***************************************************************************** // Private methods *****************************************************************************
private fun hasMoreInCache(direction: Timeline.Direction): Boolean { private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
return Realm.getInstance(realmConfiguration).use { localRealm -> return Realm.getInstance(realmConfiguration).use { localRealm ->
val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction) val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction)
?: return false ?: return false
if (direction == Timeline.Direction.FORWARDS) { if (direction == Timeline.Direction.FORWARDS) {
if (findCurrentChunk(localRealm)?.isLastForward == true) { if (findCurrentChunk(localRealm)?.isLastForward == true) {
return false return false
@ -347,11 +329,9 @@ internal class DefaultTimeline(
val sendingEvents = ArrayList<TimelineEvent>() val sendingEvents = ArrayList<TimelineEvent>()
if (hasReachedEnd(Timeline.Direction.FORWARDS)) { if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
roomEntity?.sendingTimelineEvents roomEntity?.sendingTimelineEvents
?.where() ?.filter { allowedTypes?.contains(it.root?.type) ?: false }
?.filterEventsWithSettings()
?.findAll()
?.forEach { ?.forEach {
sendingEvents.add(timelineEventMapper.map(it)) sendingEvents.add(it.asDomain())
} }
} }
return sendingEvents return sendingEvents
@ -398,7 +378,7 @@ internal class DefaultTimeline(
if (initialEventId != null && shouldFetchInitialEvent) { if (initialEventId != null && shouldFetchInitialEvent) {
fetchEvent(initialEventId) fetchEvent(initialEventId)
} else { } else {
val count = Math.min(settings.initialSize, liveEvents.size) val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size)
if (isLive) { if (isLive) {
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count) paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
} else { } else {
@ -415,9 +395,9 @@ internal class DefaultTimeline(
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
val token = getTokenLive(direction) ?: return val token = getTokenLive(direction) ?: return
val params = PaginationTask.Params(roomId = roomId, val params = PaginationTask.Params(roomId = roomId,
from = token, from = token,
direction = direction.toPaginationDirection(), direction = direction.toPaginationDirection(),
limit = limit) limit = limit)
Timber.v("Should fetch $limit items $direction") Timber.v("Should fetch $limit items $direction")
cancelableBag += paginationTask cancelableBag += paginationTask
@ -483,11 +463,10 @@ internal class DefaultTimeline(
nextDisplayIndex = offsetIndex + 1 nextDisplayIndex = offsetIndex + 1
} }
offsetResults.forEach { eventEntity -> offsetResults.forEach { eventEntity ->
val timelineEvent = eventEntity.asDomain()
val timelineEvent = buildTimelineEvent(eventEntity)
if (timelineEvent.isEncrypted() if (timelineEvent.isEncrypted()
&& timelineEvent.root.mxDecryptionResult == null) { && timelineEvent.root.mxDecryptionResult == null) {
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
} }
@ -502,12 +481,6 @@ internal class DefaultTimeline(
return offsetResults.size return offsetResults.size
} }
private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map(
timelineEventEntity = eventEntity,
buildReadReceipts = settings.buildReadReceipts,
correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
)
/** /**
* This has to be called on TimelineThread as it access realm live results * This has to be called on TimelineThread as it access realm live results
*/ */
@ -525,6 +498,7 @@ internal class DefaultTimeline(
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex) .greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
} }
return offsetQuery return offsetQuery
.filterAllowedTypes()
.limit(count) .limit(count)
.findAll() .findAll()
} }
@ -571,7 +545,7 @@ internal class DefaultTimeline(
debouncer.debounce("post_snapshot", runnable, 50) debouncer.debounce("post_snapshot", runnable, 50)
} }
// Extension methods *************************************************************************** // Extension methods ***************************************************************************
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection { private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
@ -583,20 +557,16 @@ internal class DefaultTimeline(
} else { } else {
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING) sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
} }
.filterEventsWithSettings() .filterAllowedTypes()
.findFirst() .findFirst()
} }
private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> { private fun RealmQuery<TimelineEventEntity>.filterAllowedTypes(): RealmQuery<TimelineEventEntity> {
if (settings.filterTypes) { if (allowedTypes != null) {
`in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray()) `in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray())
}
if (settings.filterEdits) {
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
} }
return this return this
} }
} }
private data class PaginationState( private data class PaginationState(

View File

@ -18,38 +18,28 @@ package im.vector.matrix.android.internal.session.room.timeline
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.timeline.Timeline 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.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineService import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.internal.database.RealmLiveData import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.fetchCopyMap import im.vector.matrix.android.internal.util.fetchCopyMap
import javax.inject.Inject
internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, internal class DefaultTimelineService @Inject constructor(private val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask
private val timelineEventMapper: TimelineEventMapper,
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
) : TimelineService { ) : TimelineService {
@AssistedInject.Factory override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
interface Factory {
fun create(roomId: String): TimelineService
}
override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
return DefaultTimeline(roomId, return DefaultTimeline(roomId,
eventId, eventId,
monarchy.realmConfiguration, monarchy.realmConfiguration,
@ -57,10 +47,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
contextOfEventTask, contextOfEventTask,
paginationTask, paginationTask,
cryptoService, cryptoService,
timelineEventMapper, allowedTypes)
settings,
TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings)
)
} }
override fun getTimeLineEvent(eventId: String): TimelineEvent? { override fun getTimeLineEvent(eventId: String): TimelineEvent? {
@ -68,7 +55,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
.fetchCopyMap({ .fetchCopyMap({
TimelineEventEntity.where(it, eventId = eventId).findFirst() TimelineEventEntity.where(it, eventId = eventId).findFirst()
}, { entity, realm -> }, { entity, realm ->
timelineEventMapper.map(entity) entity.asDomain()
}) })
} }
@ -76,8 +63,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
val liveData = RealmLiveData(monarchy.realmConfiguration) { val liveData = RealmLiveData(monarchy.realmConfiguration) {
TimelineEventEntity.where(it, eventId = eventId) TimelineEventEntity.where(it, eventId = eventId)
} }
return Transformations.map(liveData) { events -> return Transformations.map(liveData) {
events.firstOrNull()?.let { timelineEventMapper.map(it) } it.firstOrNull()?.asDomain()
} }
} }

View File

@ -1,153 +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.matrix.android.internal.session.room.timeline
import android.util.SparseArray
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntityFields
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
import im.vector.matrix.android.internal.database.query.FilterContent
import im.vector.matrix.android.internal.database.query.whereInRoom
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.RealmResults
/**
* This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering).
* When an hidden event has read receipts, we want to transfer these read receipts on the first older displayed event.
* It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription.
*/
internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
private val roomId: String,
private val settings: TimelineSettings) {
interface Delegate {
fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean
fun onReadReceiptsUpdated()
}
private val correctedReadReceiptsEventByIndex = SparseArray<String>()
private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
private lateinit var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity>
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
private lateinit var delegate: Delegate
private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet ->
var hasChange = false
// Deletion here means we don't have any readReceipts for the given hidden events
changeSet.deletions.forEach {
val eventId = correctedReadReceiptsEventByIndex[it]
val timelineEvent = liveEvents.where()
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
.findFirst()
// We are rebuilding the corresponding event with only his own RR
val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts)
hasChange = delegate.rebuildEvent(eventId, readReceipts) || hasChange
}
correctedReadReceiptsEventByIndex.clear()
correctedReadReceiptsByEvent.clear()
hiddenReadReceipts.forEachIndexed { index, summary ->
val timelineEvent = summary?.timelineEvent?.firstOrNull()
val displayIndex = timelineEvent?.root?.displayIndex
if (displayIndex != null) {
// Then we are looking for the first displayable event after the hidden one
val firstDisplayedEvent = liveEvents.where()
.lessThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, displayIndex)
.findFirst()
// If we find one, we should
if (firstDisplayedEvent != null) {
correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId)
correctedReadReceiptsByEvent
.getOrPut(firstDisplayedEvent.eventId, {
ArrayList(readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts))
})
.addAll(readReceiptsSummaryMapper.map(summary))
}
}
}
if (correctedReadReceiptsByEvent.isNotEmpty()) {
correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) ->
val sortedReadReceipts = correctedReadReceipts.sortedByDescending {
it.originServerTs
}
hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange
}
}
if (hasChange) {
delegate.onReadReceiptsUpdated()
}
}
/**
* Start the realm query subscription. Has to be called on an HandlerThread
*/
fun start(realm: Realm, liveEvents: RealmResults<TimelineEventEntity>, delegate: Delegate) {
this.liveEvents = liveEvents
this.delegate = delegate
// We are looking for read receipts set on hidden events.
// We only accept those with a timelineEvent (so coming from pagination/sync).
this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId)
.isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT)
.isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`)
.filterReceiptsWithSettings()
.findAllAsync()
.also { it.addChangeListener(hiddenReadReceiptsListener) }
}
/**
* Dispose the realm query subscription. Has to be called on an HandlerThread
*/
fun dispose() {
this.hiddenReadReceipts.removeAllChangeListeners()
}
/**
* Return the current corrected [ReadReceipt] list for an event, or null
*/
fun correctedReadReceipts(eventId: String?): List<ReadReceipt>? {
return correctedReadReceiptsByEvent[eventId]
}
/**
* We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method.
*/
private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
beginGroup()
if (settings.filterTypes) {
not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray())
}
if (settings.filterTypes && settings.filterEdits) {
or()
}
if (settings.filterEdits) {
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", FilterContent.EDIT_TYPE)
}
endGroup()
return this
}
}

View File

@ -17,10 +17,6 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
import im.vector.matrix.android.internal.database.query.createUnmanaged
import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm import io.realm.Realm
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -33,70 +29,34 @@ import javax.inject.Inject
// dict value ts value // dict value ts value
typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, Double>>>> typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, Double>>>>
private const val READ_KEY = "m.read"
private const val TIMESTAMP_KEY = "ts"
internal class ReadReceiptHandler @Inject constructor() { internal class ReadReceiptHandler @Inject constructor() {
fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) { fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?) {
if (content == null) { if (content == null) {
return return
} }
try { try {
handleReadReceiptContent(realm, roomId, content, isInitialSync) val readReceipts = mapContentToReadReceiptEntities(roomId, content)
realm.insertOrUpdate(readReceipts)
} catch (exception: Exception) { } catch (exception: Exception) {
Timber.e("Fail to handle read receipt for room $roomId") Timber.e("Fail to handle read receipt for room $roomId")
} }
} }
private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) { private fun mapContentToReadReceiptEntities(roomId: String, content: ReadReceiptContent): List<ReadReceiptEntity> {
if (isInitialSync) { return content
initialSyncStrategy(realm, roomId, content) .flatMap { (eventId, receiptDict) ->
} else { receiptDict
incrementalSyncStrategy(realm, roomId, content) .filterKeys { it == "m.read" }
} .flatMap { (_, userIdsDict) ->
} userIdsDict.map { (userId, paramsDict) ->
val ts = paramsDict.filterKeys { it == "ts" }
.values
private fun initialSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { .firstOrNull() ?: 0.0
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>() val primaryKey = roomId + userId
for ((eventId, receiptDict) in content) { ReadReceiptEntity(primaryKey, userId, eventId, roomId, ts)
val userIdsDict = receiptDict[READ_KEY] ?: continue }
val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId) }
for ((userId, paramsDict) in userIdsDict) {
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, ts)
readReceiptsSummary.readReceipts.add(receiptEntity)
}
readReceiptSummaries.add(readReceiptsSummary)
}
realm.insertOrUpdate(readReceiptSummaries)
}
private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
for ((eventId, receiptDict) in content) {
val userIdsDict = receiptDict[READ_KEY] ?: continue
val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
?: realm.createObject(ReadReceiptsSummaryEntity::class.java, eventId).apply {
this.roomId = roomId
}
for ((userId, paramsDict) in userIdsDict) {
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId)
// ensure new ts is superior to the previous one
if (ts > receiptEntity.originServerTs) {
ReadReceiptsSummaryEntity.where(realm, receiptEntity.eventId).findFirst()?.also {
it.readReceipts.remove(receiptEntity)
}
receiptEntity.eventId = eventId
receiptEntity.originServerTs = ts
readReceiptsSummary.readReceipts.add(receiptEntity)
} }
}
}
} }
} }

View File

@ -62,11 +62,11 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy() data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
} }
fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) { fun handle(roomsSyncResponse: RoomsSyncResponse, reporter: DefaultInitialSyncProgressService? = null) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter) handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter)
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter) handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter)
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter)
} }
//handle event for bing rule checks //handle event for bing rule checks
@ -89,12 +89,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
// PRIVATE METHODS ***************************************************************************** // PRIVATE METHODS *****************************************************************************
private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService?) { private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, reporter: DefaultInitialSyncProgressService?) {
val rooms = when (handlingStrategy) { val rooms = when (handlingStrategy) {
is HandlingStrategy.JOINED -> is HandlingStrategy.JOINED ->
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) { handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) {
handleJoinedRoom(realm, it.key, it.value, isInitialSync) handleJoinedRoom(realm, it.key, it.value)
} }
is HandlingStrategy.INVITED -> is HandlingStrategy.INVITED ->
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.4f) { handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.4f) {
@ -112,21 +112,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private fun handleJoinedRoom(realm: Realm, private fun handleJoinedRoom(realm: Realm,
roomId: String, roomId: String,
roomSync: RoomSync, roomSync: RoomSync): RoomEntity {
isInitalSync: Boolean): RoomEntity {
Timber.v("Handle join sync for room $roomId") Timber.v("Handle join sync for room $roomId")
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
handleEphemeral(realm, roomId, roomSync.ephemeral, isInitalSync)
}
if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) {
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
}
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
if (roomEntity.membership == Membership.INVITE) { if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -136,7 +127,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
// State event // State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
?: Int.MIN_VALUE ?: Int.MIN_VALUE
val untimelinedStateIndex = minStateIndex + 1 val untimelinedStateIndex = minStateIndex + 1
roomSync.state.events.forEach { event -> roomSync.state.events.forEach { event ->
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
@ -159,6 +150,14 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomEntity.addOrUpdate(chunkEntity) roomEntity.addOrUpdate(chunkEntity)
} }
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications) roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
handleEphemeral(realm, roomId, roomSync.ephemeral)
}
if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) {
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
}
return roomEntity return roomEntity
} }
@ -168,7 +167,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
InvitedRoomSync): RoomEntity { InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId") Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events) val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
@ -182,7 +181,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomId: String, roomId: String,
roomSync: RoomSync): RoomEntity { roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
roomEntity.membership = Membership.LEAVE roomEntity.membership = Membership.LEAVE
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -234,21 +233,17 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
} }
@Suppress("UNCHECKED_CAST")
private fun handleEphemeral(realm: Realm, private fun handleEphemeral(realm: Realm,
roomId: String, roomId: String,
ephemeral: RoomSyncEphemeral, ephemeral: RoomSyncEphemeral) {
isInitalSync: Boolean) { ephemeral.events
for (event in ephemeral.events) { .filter { it.getClearType() == EventType.RECEIPT }
if (event.type != EventType.RECEIPT) continue .map { it.content.toModel<ReadReceiptContent>() }
val readReceiptContent = event.content as? ReadReceiptContent ?: continue .forEach { readReceiptHandler.handle(realm, roomId, it) }
readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitalSync)
}
} }
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) { private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
accountData.events accountData.events
.asSequence()
.filter { it.getClearType() == EventType.TAG } .filter { it.getClearType() == EventType.TAG }
.map { it.content.toModel<RoomTagContent>() } .map { it.content.toModel<RoomTagContent>() }
.forEach { roomTagHandler.handle(realm, roomId, it) } .forEach { roomTagHandler.handle(realm, roomId, it) }

View File

@ -66,7 +66,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) { reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) {
if (syncResponse.rooms != null) { if (syncResponse.rooms != null) {
roomSyncHandler.handle(syncResponse.rooms, isInitialSync, reporter) roomSyncHandler.handle(syncResponse.rooms, reporter)
} }
} }
}.also { }.also {

View File

@ -30,7 +30,6 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import kotlinx.coroutines.CancellationException
import timber.log.Timber import timber.log.Timber
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -71,8 +70,6 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
if (state is SyncState.RUNNING) { if (state is SyncState.RUNNING) {
Timber.v("Pause sync...") Timber.v("Pause sync...")
updateStateTo(SyncState.PAUSED) updateStateTo(SyncState.PAUSED)
cancelableTask?.cancel()
lock.notify()
} }
} }
@ -93,25 +90,18 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
while (state != SyncState.KILLING) { while (state != SyncState.KILLING) {
Timber.v("Entering loop, state: $state")
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) { if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
Timber.v("No network or sync is Paused. Waiting...") Timber.v("Sync is Paused. Waiting...")
synchronized(lock) { synchronized(lock) {
lock.wait() lock.wait()
} }
Timber.v("...unlocked")
} else { } else {
if (state !is SyncState.RUNNING) { if (state !is SyncState.RUNNING) {
updateStateTo(SyncState.RUNNING(afterPause = true)) updateStateTo(SyncState.RUNNING(afterPause = true))
} }
Timber.v("[$this] Execute sync request with timeout $DEFAULT_LONG_POOL_TIMEOUT")
// No timeout after a pause
val timeout = state.let { if (it is SyncState.RUNNING && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
Timber.v("Execute sync request with timeout $timeout")
val latch = CountDownLatch(1) val latch = CountDownLatch(1)
val params = SyncTask.Params(timeout) val params = SyncTask.Params(DEFAULT_LONG_POOL_TIMEOUT)
cancelableTask = syncTask.configureWith(params) { cancelableTask = syncTask.configureWith(params) {
this.callbackThread = TaskThread.SYNC this.callbackThread = TaskThread.SYNC
@ -119,31 +109,29 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.v("onSuccess")
latch.countDown() latch.countDown()
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) { if (failure is Failure.NetworkConnection
&& failure.cause is SocketTimeoutException) {
// Timeout are not critical // Timeout are not critical
Timber.v("Timeout") Timber.v("Timeout")
} else if (failure is Failure.Unknown && failure.throwable is CancellationException) {
Timber.v("Cancelled")
} else if (failure is Failure.ServerError
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// No token or invalid token, stop the thread
Timber.w(failure)
updateStateTo(SyncState.KILLING)
} else { } else {
Timber.e(failure) Timber.e(failure)
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
// Wait 10s before retrying
Timber.v("Wait 10s")
sleep(RETRY_WAIT_TIME_MS)
}
} }
if (failure !is Failure.NetworkConnection
|| failure.cause is JsonEncodingException) {
// Wait 10s before retrying
sleep(RETRY_WAIT_TIME_MS)
}
if (failure is Failure.ServerError
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// No token or invalid token, stop the thread
updateStateTo(SyncState.KILLING)
}
latch.countDown() latch.countDown()
} }
} }
@ -151,10 +139,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
.executeBy(taskExecutor) .executeBy(taskExecutor)
latch.await() latch.await()
state.let { if (state is SyncState.RUNNING) {
if (it is SyncState.RUNNING && it.afterPause) { updateStateTo(SyncState.RUNNING(afterPause = false))
updateStateTo(SyncState.RUNNING(afterPause = false))
}
} }
Timber.v("...Continue") Timber.v("...Continue")

View File

@ -29,15 +29,7 @@ static def generateVersionCodeFromTimestamp() {
} }
def generateVersionCodeFromVersionName() { def generateVersionCodeFromVersionName() {
return versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch return versionMajor * 10000 + versionMinor * 100 + versionPatch
}
def getVersionCode() {
if (gitBranchName() == "develop") {
return generateVersionCodeFromTimestamp()
} else {
return generateVersionCodeFromVersionName()
}
} }
static def gitRevision() { static def gitRevision() {
@ -55,14 +47,6 @@ static def gitBranchName() {
return cmd.execute().text.trim() return cmd.execute().text.trim()
} }
static def getVersionSuffix() {
if (gitBranchName() == "master") {
return ""
} else {
return "-dev"
}
}
project.android.buildTypes.all { buildType -> project.android.buildTypes.all { buildType ->
buildType.javaCompileOptions.annotationProcessorOptions.arguments = buildType.javaCompileOptions.annotationProcessorOptions.arguments =
[ [
@ -87,11 +71,11 @@ android {
targetSdkVersion 28 targetSdkVersion 28
multiDexEnabled true multiDexEnabled true
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode. // For release, use generateVersionCodeFromVersionName()
// Other branches (master, features, etc.) will have version code based on application version. versionCode generateVersionCodeFromTimestamp()
versionCode project.getVersionCode() //versionCode generateVersionCodeFromVersionName()
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getVersionSuffix()}" versionName "${versionMajor}.${versionMinor}.${versionPatch}-dev"
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
resValue "string", "git_revision", "\"${gitRevision()}\"" resValue "string", "git_revision", "\"${gitRevision()}\""
@ -133,10 +117,9 @@ android {
} }
} }
applicationVariants.all { variant -> android.applicationVariants.all { variant ->
variant.outputs.each { output -> variant.outputs.each { output ->
def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI)) def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI))
// Known limitation: it does not modify the value in the BuildConfig.java generated file
output.versionCodeOverride = baseAbiVersionCode * 10_000_000 + variant.versionCode output.versionCodeOverride = baseAbiVersionCode * 10_000_000 + variant.versionCode
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.riotx.fdroid.features.settings.troubleshoot package im.vector.riotx.fdroid.features.settings.troubleshoot
import androidx.appcompat.app.AppCompatActivity
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
@ -24,12 +25,12 @@ import javax.inject.Inject
/** /**
* Test that the application is started on boot * Test that the application is started on boot
*/ */
class TestAutoStartBoot @Inject constructor(private val vectorPreferences: VectorPreferences, class TestAutoStartBoot @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) { : TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
override fun perform() { override fun perform() {
if (vectorPreferences.autoStartOnBoot()) { if (VectorPreferences.autoStartOnBoot(context)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success) description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
quickFix = null quickFix = null
@ -37,7 +38,7 @@ class TestAutoStartBoot @Inject constructor(private val vectorPreferences: Vecto
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_failed) description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_failed)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
override fun doFix() { override fun doFix() {
vectorPreferences.setAutoStartOnBoot(true) VectorPreferences.setAutoStartOnBoot(context, true)
manager?.retry() manager?.retry()
} }
} }

View File

@ -63,9 +63,9 @@ object FcmHelper {
AlarmSyncBroadcastReceiver.cancelAlarm(context) AlarmSyncBroadcastReceiver.cancelAlarm(context)
} }
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
//We need to use alarm in this mode //We need to use alarm in this mode
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { if (VectorPreferences.areNotificationEnabledForDevice(context) && activeSessionHolder.hasActiveSession()) {
val currentSession = activeSessionHolder.getActiveSession() val currentSession = activeSessionHolder.getActiveSession()
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L) AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L)
Timber.i("Alarm scheduled to restart service") Timber.i("Alarm scheduled to restart service")

View File

@ -52,7 +52,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private lateinit var notifiableEventResolver: NotifiableEventResolver private lateinit var notifiableEventResolver: NotifiableEventResolver
private lateinit var pusherManager: PushersManager private lateinit var pusherManager: PushersManager
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences
// UI handler // UI handler
private val mUIHandler by lazy { private val mUIHandler by lazy {
@ -65,7 +64,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
notifiableEventResolver = vectorComponent().notifiableEventResolver() notifiableEventResolver = vectorComponent().notifiableEventResolver()
pusherManager = vectorComponent().pusherManager() pusherManager = vectorComponent().pusherManager()
activeSessionHolder = vectorComponent().activeSessionHolder() activeSessionHolder = vectorComponent().activeSessionHolder()
vectorPreferences = vectorComponent().vectorPreferences()
} }
/** /**
@ -74,7 +72,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* @param message the message * @param message the message
*/ */
override fun onMessageReceived(message: RemoteMessage?) { override fun onMessageReceived(message: RemoteMessage?) {
if (!vectorPreferences.areNotificationEnabledForDevice()) { if (!VectorPreferences.areNotificationEnabledForDevice(applicationContext)) {
Timber.i("Notification are disabled for this device") Timber.i("Notification are disabled for this device")
return return
} }
@ -109,7 +107,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
if (refreshedToken == null) { if (refreshedToken == null) {
Timber.w("onNewToken:received null token") Timber.w("onNewToken:received null token")
} else { } else {
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { if (VectorPreferences.areNotificationEnabledForDevice(applicationContext) && activeSessionHolder.hasActiveSession()) {
pusherManager.registerPusherWithFcmKey(refreshedToken) pusherManager.registerPusherWithFcmKey(refreshedToken)
} }
} }

View File

@ -27,7 +27,6 @@ import com.google.firebase.iid.FirebaseInstanceId
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.core.pushers.PushersManager
import im.vector.riotx.features.settings.VectorPreferences
import timber.log.Timber import timber.log.Timber
/** /**
@ -106,7 +105,7 @@ object FcmHelper {
// No op // No op
} }
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
// TODO FCM fallback // TODO FCM fallback
} }
} }

View File

@ -42,7 +42,6 @@ import im.vector.riotx.core.di.DaggerVectorComponent
import im.vector.riotx.core.di.HasVectorInjector import im.vector.riotx.core.di.HasVectorInjector
import im.vector.riotx.core.di.VectorComponent import im.vector.riotx.core.di.VectorComponent
import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.utils.initKnownEmojiHashSet
import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationDrawerManager
@ -50,12 +49,12 @@ import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.rageshake.VectorFileLogger import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.version.getVersion
import im.vector.riotx.features.version.VersionProvider
import im.vector.riotx.push.fcm.FcmHelper import im.vector.riotx.push.fcm.FcmHelper
import timber.log.Timber import timber.log.Timber
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import im.vector.riotx.core.utils.initKnownEmojiHashSet
import javax.inject.Inject import javax.inject.Inject
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider { class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
@ -70,8 +69,6 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
@Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener @Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener
@Inject lateinit var vectorPreferences: VectorPreferences
@Inject lateinit var versionProvider: VersionProvider
lateinit var vectorComponent: VectorComponent lateinit var vectorComponent: VectorComponent
private var fontThreadHandler: Handler? = null private var fontThreadHandler: Handler? = null
@ -125,7 +122,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
fun entersBackground() { fun entersBackground() {
Timber.i("App entered background") // call persistInfo Timber.i("App entered background") // call persistInfo
notificationDrawerManager.persistInfo() notificationDrawerManager.persistInfo()
FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder) FcmHelper.onEnterBackground(appContext, activeSessionHolder)
} }
}) })
//This should be done as early as possible //This should be done as early as possible
@ -141,7 +138,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
} }
private fun logInfo() { private fun logInfo() {
val appVersion = versionProvider.getVersion(longFormat = true, useBuildNumber = true) val appVersion = getVersion(longFormat = true, useBuildNumber = true)
val sdkVersion = Matrix.getSdkVersion() val sdkVersion = Matrix.getSdkVersion()
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date()) val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())

View File

@ -1,24 +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.core.di
import com.squareup.inject.assisted.dagger2.AssistedModule
import dagger.Module
@AssistedModule
@Module(includes = [AssistedInject_AssistedInjectModule::class])
interface AssistedInjectModule

View File

@ -41,12 +41,7 @@ import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsers
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
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.readreceipts.DisplayReadReceiptsBottomSheet 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.MessageMenuFragment
import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionFragment
import im.vector.riotx.features.home.room.detail.timeline.action.ViewEditHistoryBottomSheet
import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
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
@ -65,15 +60,12 @@ import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.settings.VectorSettingsActivity
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
import im.vector.riotx.features.settings.push.PushGatewaysFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment
@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class]) @Component(dependencies = [VectorComponent::class], modules = [ViewModelModule::class, HomeModule::class])
@ScreenScope @ScreenScope
interface ScreenComponent { interface ScreenComponent {
@ -161,12 +153,6 @@ interface ScreenComponent {
fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment) fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)
fun inject(vectorSettingsAdvancedNotificationPreferenceFragment: VectorSettingsAdvancedNotificationPreferenceFragment)
fun inject(vectorSettingsSecurityPrivacyFragment: VectorSettingsSecurityPrivacyFragment)
fun inject(vectorSettingsHelpAboutFragment: VectorSettingsHelpAboutFragment)
fun inject(userAvatarPreference: UserAvatarPreference) fun inject(userAvatarPreference: UserAvatarPreference)
fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment) fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment)
@ -179,8 +165,6 @@ interface ScreenComponent {
fun inject(createDirectRoomActivity: CreateDirectRoomActivity) fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
fun inject(displayReadReceiptsBottomSheet: DisplayReadReceiptsBottomSheet)
@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(vectorComponent: VectorComponent, fun create(vectorComponent: VectorComponent,

View File

@ -41,7 +41,6 @@ import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Singleton import javax.inject.Singleton
@Component(modules = [VectorModule::class]) @Component(modules = [VectorModule::class])
@ -96,8 +95,6 @@ interface VectorComponent {
fun notifiableEventResolver(): NotifiableEventResolver fun notifiableEventResolver(): NotifiableEventResolver
fun vectorPreferences(): VectorPreferences
@Component.Factory @Component.Factory
interface Factory { interface Factory {
fun create(@BindsInstance context: Context): VectorComponent fun create(@BindsInstance context: Context): VectorComponent

View File

@ -25,17 +25,41 @@ import im.vector.riotx.core.platform.ConfigurationViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
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.HomeNavigationViewModel import im.vector.riotx.features.home.*
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel_AssistedFactory
import im.vector.riotx.features.home.group.GroupListViewModel
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_AssistedFactory
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel_AssistedFactory
import im.vector.riotx.features.home.room.detail.timeline.action.*
import im.vector.riotx.features.home.room.list.RoomListViewModel
import im.vector.riotx.features.home.room.list.RoomListViewModel_AssistedFactory
import im.vector.riotx.features.reactions.EmojiChooserViewModel import im.vector.riotx.features.reactions.EmojiChooserViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectoryViewModel
import im.vector.riotx.features.roomdirectory.RoomDirectoryViewModel_AssistedFactory
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel_AssistedFactory
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerViewModel
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerViewModel_AssistedFactory
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewViewModel
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewViewModel_AssistedFactory
import im.vector.riotx.features.settings.push.PushGatewaysViewModel
import im.vector.riotx.features.settings.push.PushGatewaysViewModel_AssistedFactory
import im.vector.riotx.features.workers.signout.SignOutViewModel import im.vector.riotx.features.workers.signout.SignOutViewModel
@Module @Module
interface ViewModelModule { interface ViewModelModule {
/** /**
* ViewModels with @IntoMap will be injected by this factory * ViewModels with @IntoMap will be injected by this factory
*/ */
@ -45,7 +69,6 @@ interface ViewModelModule {
/** /**
* Below are bindings for the androidx view models (which extend ViewModel). Will be converted to MvRx ViewModel in the future. * Below are bindings for the androidx view models (which extend ViewModel). Will be converted to MvRx ViewModel in the future.
*/ */
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(SignOutViewModel::class) @ViewModelKey(SignOutViewModel::class)
@ -101,4 +124,62 @@ interface ViewModelModule {
@ViewModelKey(CreateDirectRoomNavigationViewModel::class) @ViewModelKey(CreateDirectRoomNavigationViewModel::class)
fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel
/**
* Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future.
*/
@Binds
fun bindHomeActivityViewModelFactory(factory: HomeActivityViewModel_AssistedFactory): HomeActivityViewModel.Factory
@Binds
fun bindTextComposerViewModelFactory(factory: TextComposerViewModel_AssistedFactory): TextComposerViewModel.Factory
@Binds
fun bindRoomDetailViewModelFactory(factory: RoomDetailViewModel_AssistedFactory): RoomDetailViewModel.Factory
@Binds
fun bindQuickReactionViewModelFactory(factory: QuickReactionViewModel_AssistedFactory): QuickReactionViewModel.Factory
@Binds
fun bindMessageActionsViewModelFactory(factory: MessageActionsViewModel_AssistedFactory): MessageActionsViewModel.Factory
@Binds
fun bindMessageMenuViewModelFactory(factory: MessageMenuViewModel_AssistedFactory): MessageMenuViewModel.Factory
@Binds
fun bindRoomListViewModelFactory(factory: RoomListViewModel_AssistedFactory): RoomListViewModel.Factory
@Binds
fun bindGroupListViewModelFactory(factory: GroupListViewModel_AssistedFactory): GroupListViewModel.Factory
@Binds
fun bindHomeDetailViewModelFactory(factory: HomeDetailViewModel_AssistedFactory): HomeDetailViewModel.Factory
@Binds
fun bindKeysBackupSettingsViewModelFactory(factory: KeysBackupSettingsViewModel_AssistedFactory): KeysBackupSettingsViewModel.Factory
@Binds
fun bindRoomDirectoryPickerViewModelFactory(factory: RoomDirectoryPickerViewModel_AssistedFactory): RoomDirectoryPickerViewModel.Factory
@Binds
fun bindRoomDirectoryViewModelFactory(factory: RoomDirectoryViewModel_AssistedFactory): RoomDirectoryViewModel.Factory
@Binds
fun bindRoomPreviewViewModelFactory(factory: RoomPreviewViewModel_AssistedFactory): RoomPreviewViewModel.Factory
@Binds
fun bindViewReactionViewModelFactory(factory: ViewReactionViewModel_AssistedFactory): ViewReactionViewModel.Factory
@Binds
fun bindViewEditHistoryViewModelFactory(factory: ViewEditHistoryViewModel_AssistedFactory): ViewEditHistoryViewModel.Factory
@Binds
fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
@Binds
fun bindCreateDirectRoomViewModelFactory(factory: CreateDirectRoomViewModel_AssistedFactory): CreateDirectRoomViewModel.Factory
@Binds
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
} }

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.core.ui.views package im.vector.riotx.core.platform
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
@ -32,7 +32,10 @@ import androidx.core.content.ContextCompat
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.permalinks.PermalinkFactory
import im.vector.matrix.android.api.session.events.model.Event 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.tombstone.RoomTombstoneContent
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.error.ResourceLimitErrorFormatter import im.vector.riotx.core.error.ResourceLimitErrorFormatter
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils

View File

@ -161,6 +161,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
unBinder?.unbind() unBinder?.unbind()
unBinder = null unBinder = null

View File

@ -16,17 +16,13 @@
package im.vector.riotx.core.resources package im.vector.riotx.core.resources
import android.content.Context
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Inject import javax.inject.Inject
class UserPreferencesProvider @Inject constructor(private val vectorPreferences: VectorPreferences) { class UserPreferencesProvider @Inject constructor(private val context: Context) {
fun shouldShowHiddenEvents(): Boolean { fun shouldShowHiddenEvents(): Boolean {
return vectorPreferences.shouldShowHiddenEvents() return VectorPreferences.shouldShowHiddenEvents(context)
} }
fun shouldShowReadReceipts(): Boolean {
return vectorPreferences.showReadReceipts()
}
} }

View File

@ -1,40 +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.core.resources
import android.content.Context
import android.os.Build
import androidx.annotation.NonNull
import javax.inject.Inject
class VersionCodeProvider @Inject constructor(private val context: Context) {
/**
* Returns the version code, read from the Manifest. It is not the same than BuildConfig.VERSION_CODE due to versionCodeOverride
*/
@NonNull
fun getVersionCode(): Long {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode
} else {
@Suppress("DEPRECATION")
packageInfo.versionCode.toLong()
}
}
}

View File

@ -1,80 +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.core.ui.views
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import butterknife.ButterKnife
import im.vector.riotx.R
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import kotlinx.android.synthetic.main.view_read_receipts.view.*
private const val MAX_RECEIPT_DISPLAYED = 5
class ReadReceiptsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private val receiptAvatars: List<ImageView> by lazy {
listOf(receiptAvatar1, receiptAvatar2, receiptAvatar3, receiptAvatar4, receiptAvatar5)
}
init {
setupView()
}
private fun setupView() {
inflate(context, R.layout.view_read_receipts, this)
ButterKnife.bind(this)
}
fun render(readReceipts: List<ReadReceiptData>, avatarRenderer: AvatarRenderer, clickListener: OnClickListener) {
setOnClickListener(clickListener)
if (readReceipts.isNotEmpty()) {
isVisible = true
for (index in 0 until MAX_RECEIPT_DISPLAYED) {
val receiptData = readReceipts.getOrNull(index)
if (receiptData == null) {
receiptAvatars[index].visibility = View.INVISIBLE
} else {
receiptAvatars[index].visibility = View.VISIBLE
avatarRenderer.render(receiptData.avatarUrl, receiptData.userId, receiptData.displayName, receiptAvatars[index])
}
}
if (readReceipts.size > MAX_RECEIPT_DISPLAYED) {
receiptMore.visibility = View.VISIBLE
receiptMore.text = context.getString(
R.string.x_plus, readReceipts.size - MAX_RECEIPT_DISPLAYED
)
} else {
receiptMore.visibility = View.GONE
}
} else {
isVisible = false
}
}
}

View File

@ -65,7 +65,6 @@ 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.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
@ -77,7 +76,7 @@ import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.core.files.addEntryToDownloadManager import im.vector.riotx.core.files.addEntryToDownloadManager
import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.ui.views.NotificationAreaView import im.vector.riotx.core.platform.NotificationAreaView
import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.platform.VectorBaseFragment
import im.vector.riotx.core.utils.* import im.vector.riotx.core.utils.*
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
@ -92,7 +91,6 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerActions
import im.vector.riotx.features.home.room.detail.composer.TextComposerView 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.readreceipts.DisplayReadReceiptsBottomSheet
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.* import im.vector.riotx.features.home.room.detail.timeline.action.*
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
@ -186,7 +184,6 @@ class RoomDetailFragment :
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
@Inject lateinit var eventHtmlRenderer: EventHtmlRenderer @Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
@Inject lateinit var vectorPreferences: VectorPreferences
override fun getLayoutResId() = R.layout.fragment_room_detail override fun getLayoutResId() = R.layout.fragment_room_detail
@ -249,14 +246,6 @@ class RoomDetailFragment :
is SendMode.REPLY -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_reply, false) is SendMode.REPLY -> enterSpecialMode(mode.timelineEvent, R.drawable.ic_reply, false)
} }
} }
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState ->
syncProgressBar.visibility = when (syncState) {
is SyncState.RUNNING -> if (syncState.afterPause) View.VISIBLE else View.GONE
else -> View.GONE
}
syncProgressBarWrap.visibility = syncProgressBar.visibility
}
} }
private fun setupNotificationView() { private fun setupNotificationView() {
@ -326,17 +315,17 @@ class RoomDetailFragment :
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build() val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody val document = parser.parse(messageContent.formattedBody
?: messageContent.body) ?: messageContent.body)
formattedBody = eventHtmlRenderer.render(document) formattedBody = eventHtmlRenderer.render(document)
} }
composerLayout.composerRelatedMessageContent.text = formattedBody composerLayout.composerRelatedMessageContent.text = formattedBody
?: nonFormattedBody ?: nonFormattedBody
composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "") composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "")
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
avatarRenderer.render(event.senderAvatar, event.root.senderId avatarRenderer.render(event.senderAvatar, event.root.senderId
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar) ?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length) composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
composerLayout.expand { composerLayout.expand {
@ -365,9 +354,9 @@ class RoomDetailFragment :
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data) REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
REACTION_SELECT_REQUEST_CODE -> { REACTION_SELECT_REQUEST_CODE -> {
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID) val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
?: return ?: return
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT) val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
?: return ?: return
//TODO check if already reacted with that? //TODO check if already reacted with that?
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId)) roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
} }
@ -400,28 +389,28 @@ class RoomDetailFragment :
recyclerView.setController(timelineEventController) recyclerView.setController(timelineEventController)
timelineEventController.callback = this timelineEventController.callback = this
if (vectorPreferences.swipeToReplyIsEnabled()) { if (VectorPreferences.swipeToReplyIsEnabled(requireContext())) {
val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), val swipeCallback = RoomMessageTouchHelperCallback(requireContext(),
R.drawable.ic_reply, R.drawable.ic_reply,
object : RoomMessageTouchHelperCallback.QuickReplayHandler { object : RoomMessageTouchHelperCallback.QuickReplayHandler {
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
(model as? AbsMessageItem)?.informationData?.let { (model as? AbsMessageItem)?.informationData?.let {
val eventId = it.eventId val eventId = it.eventId
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId)) roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
} }
} }
override fun canSwipeModel(model: EpoxyModel<*>): Boolean { override fun canSwipeModel(model: EpoxyModel<*>): Boolean {
return when (model) { return when (model) {
is MessageFileItem, is MessageFileItem,
is MessageImageVideoItem, is MessageImageVideoItem,
is MessageTextItem -> { is MessageTextItem -> {
return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED
} }
else -> false else -> false
} }
} }
}) })
val touchHelper = ItemTouchHelper(swipeCallback) val touchHelper = ItemTouchHelper(swipeCallback)
touchHelper.attachToRecyclerView(recyclerView) touchHelper.attachToRecyclerView(recyclerView)
} }
@ -493,7 +482,7 @@ class RoomDetailFragment :
composerLayout.sendButton.setOnClickListener { composerLayout.sendButton.setOnClickListener {
val textMessage = composerLayout.composerEditText.text.toString() val textMessage = composerLayout.composerEditText.text.toString()
if (textMessage.isNotBlank()) { if (textMessage.isNotBlank()) {
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, vectorPreferences.isMarkdownEnabled())) roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, VectorPreferences.isMarkdownEnabled(requireContext())))
} }
} }
composerLayout.composerRelatedMessageCloseButton.setOnClickListener { composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
@ -518,7 +507,7 @@ class RoomDetailFragment :
items.add(DialogListItem.SendFile) items.add(DialogListItem.SendFile)
// Send voice // Send voice
if (vectorPreferences.isSendVoiceFeatureEnabled()) { if (VectorPreferences.isSendVoiceFeatureEnabled(this)) {
items.add(DialogListItem.SendVoice.INSTANCE) items.add(DialogListItem.SendVoice.INSTANCE)
} }
@ -527,7 +516,7 @@ class RoomDetailFragment :
//items.add(DialogListItem.SendSticker) //items.add(DialogListItem.SendSticker)
// Camera // Camera
//if (vectorPreferences.useNativeCamera()) { //if (VectorPreferences.useNativeCamera(this)) {
items.add(DialogListItem.TakePhoto) items.add(DialogListItem.TakePhoto)
items.add(DialogListItem.TakeVideo) items.add(DialogListItem.TakeVideo)
//} else { //} else {
@ -649,12 +638,8 @@ class RoomDetailFragment :
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) { private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
when (sendMessageResult) { when (sendMessageResult) {
is SendMessageResult.MessageSent -> { is SendMessageResult.MessageSent,
// Clear composer
composerLayout.composerEditText.text = null
}
is SendMessageResult.SlashCommandHandled -> { is SendMessageResult.SlashCommandHandled -> {
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
// Clear composer // Clear composer
composerLayout.composerEditText.text = null composerLayout.composerEditText.text = null
} }
@ -831,11 +816,6 @@ class RoomDetailFragment :
}) })
} }
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {
DisplayReadReceiptsBottomSheet.newInstance(readReceipts)
.show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS")
}
// AutocompleteUserPresenter.Callback // AutocompleteUserPresenter.Callback
override fun onQueryUsers(query: CharSequence?) { override fun onQueryUsers(query: CharSequence?) {
@ -979,7 +959,7 @@ class RoomDetailFragment :
// vibrate = true // vibrate = true
} }
// if (vibrate && vectorPreferences.vibrateWhenMentioning()) { // if (vibrate && VectorPreferences.vibrateWhenMentioning(context)) {
// val v= context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator // val v= context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
// if (v?.hasVibrator() == true) { // if (v?.hasVibrator() == true) {
// v.vibrate(100) // v.vibrate(100)

View File

@ -43,7 +43,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
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.session.room.timeline.TimelineSettings
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
@ -57,7 +56,6 @@ import im.vector.riotx.core.utils.subscribeLogError
import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.CommandParser
import im.vector.riotx.features.command.ParsedCommand import im.vector.riotx.features.command.ParsedCommand
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
import im.vector.riotx.features.settings.VectorPreferences
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
@ -67,8 +65,7 @@ import java.util.concurrent.TimeUnit
class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState, class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
private val userPreferencesProvider: UserPreferencesProvider, userPreferencesProvider: UserPreferencesProvider,
private val vectorPreferences: VectorPreferences,
private val session: Session private val session: Session
) : VectorViewModel<RoomDetailViewState>(initialState) { ) : VectorViewModel<RoomDetailViewState>(initialState) {
@ -76,13 +73,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private val roomId = initialState.roomId private val roomId = initialState.roomId
private val eventId = initialState.eventId private val eventId = initialState.eventId
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>() private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) { private val allowedTypes = if (userPreferencesProvider.shouldShowHiddenEvents()) {
TimelineSettings(30, false, true, TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES, userPreferencesProvider.shouldShowReadReceipts()) TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES
} else { } else {
TimelineSettings(30, true, true, TimelineDisplayableEvents.DISPLAYABLE_TYPES, userPreferencesProvider.shouldShowReadReceipts()) TimelineDisplayableEvents.DISPLAYABLE_TYPES
} }
private var timeline = room.createTimeline(eventId, allowedTypes)
private var timeline = room.createTimeline(eventId, timelineSettings)
// Slot to keep a pending action during permission request // Slot to keep a pending action during permission request
var pendingAction: RoomDetailActions? = null var pendingAction: RoomDetailActions? = null
@ -105,7 +101,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
init { init {
observeSyncState()
observeRoomSummary() observeRoomSummary()
observeEventDisplayedActions() observeEventDisplayedActions()
observeSummaryState() observeSummaryState()
@ -142,7 +137,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) { private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) {
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>() val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
?: return ?: return
val roomId = tombstoneContent.replacementRoom ?: "" val roomId = tombstoneContent.replacementRoom ?: ""
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
@ -248,9 +243,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
} }
is ParsedCommand.SetMarkdown -> { is ParsedCommand.SetMarkdown -> {
vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) // TODO
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled( _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
} }
is ParsedCommand.UnbanUser -> { is ParsedCommand.UnbanUser -> {
// TODO // TODO
@ -274,7 +268,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
is ParsedCommand.SendEmote -> { is ParsedCommand.SendEmote -> {
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE) room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
} }
is ParsedCommand.ChangeTopic -> { is ParsedCommand.ChangeTopic -> {
handleChangeTopicSlashCommand(slashCommandResult) handleChangeTopicSlashCommand(slashCommandResult)
@ -289,7 +283,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
//is original event a reply? //is original event a reply?
val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel<MessageContent>()?.relatesTo?.inReplyTo?.eventId
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId ?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.relatesTo?.inReplyTo?.eventId
if (inReplyTo != null) { if (inReplyTo != null) {
//TODO check if same content? //TODO check if same content?
room.getTimeLineEvent(inReplyTo)?.let { room.getTimeLineEvent(inReplyTo)?.let {
@ -298,12 +292,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} else { } else {
val messageContent: MessageContent? = val messageContent: MessageContent? =
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
val existingBody = messageContent?.body ?: "" val existingBody = messageContent?.body ?: ""
if (existingBody != action.text) { if (existingBody != action.text) {
room.editTextMessage(state.sendMode.timelineEvent.root.eventId room.editTextMessage(state.sendMode.timelineEvent.root.eventId
?: "", messageContent?.type ?: "", messageContent?.type
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
} else { } else {
Timber.w("Same message content, do not send edition") Timber.w("Same message content, do not send edition")
} }
@ -318,7 +312,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
is SendMode.QUOTE -> { is SendMode.QUOTE -> {
val messageContent: MessageContent? = val messageContent: MessageContent? =
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: state.sendMode.timelineEvent.root.getClearContent().toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
val textMsg = messageContent?.body val textMsg = messageContent?.body
val finalText = legacyRiotQuoteText(textMsg, action.text) val finalText = legacyRiotQuoteText(textMsg, action.text)
@ -354,6 +348,8 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
} }
} }
// Handle slash command
} }
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
@ -375,7 +371,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) { private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> { room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
@ -389,7 +385,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} }
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled()) _sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
room.invite(invite.userId, object : MatrixCallback<Unit> { room.invite(invite.userId, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
@ -554,7 +550,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
} else { } else {
// change timeline // change timeline
timeline.dispose() timeline.dispose()
timeline = room.createTimeline(targetEventId, timelineSettings) timeline = room.createTimeline(targetEventId, allowedTypes)
timeline.start() timeline.start()
withState { withState {
@ -634,17 +630,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
.disposeOnClear() .disposeOnClear()
} }
private fun observeSyncState() {
session.rx()
.liveSyncState()
.subscribe { syncState ->
setState {
copy(syncState = syncState)
}
}
.disposeOnClear()
}
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.rx().liveRoomSummary() room.rx().liveRoomSummary()
.execute { async -> .execute { async ->

View File

@ -21,9 +21,9 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
import im.vector.matrix.android.api.session.room.timeline.Timeline 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.TimelineEvent
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
/** /**
@ -50,8 +50,7 @@ data class RoomDetailViewState(
val sendMode: SendMode = SendMode.REGULAR, val sendMode: SendMode = SendMode.REGULAR,
val isEncrypted: Boolean = false, val isEncrypted: Boolean = false,
val tombstoneEvent: Event? = null, val tombstoneEvent: Event? = null,
val tombstoneEventHandling: Async<String> = Uninitialized, val tombstoneEventHandling: Async<String> = Uninitialized
val syncState: SyncState = SyncState.IDLE
) : MvRxState { ) : MvRxState {
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId) constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)

View File

@ -191,13 +191,12 @@ class RoomMessageTouchHelperCallback(private val context: Context,
} }
val y = (itemView.top + itemView.measuredHeight / 2).toFloat() val y = (itemView.top + itemView.measuredHeight / 2).toFloat()
val hw = imageDrawable.intrinsicWidth / 2f //magic numbers?
val hh = imageDrawable.intrinsicHeight / 2f
imageDrawable.setBounds( imageDrawable.setBounds(
(x - hw * scale).toInt(), (x - convertToPx(12) * scale).toInt(),
(y - hh * scale).toInt(), (y - convertToPx(11) * scale).toInt(),
(x + hw * scale).toInt(), (x + convertToPx(12) * scale).toInt(),
(y + hh * scale).toInt() (y + convertToPx(10) * scale).toInt()
) )
imageDrawable.draw(canvas) imageDrawable.draw(canvas)
imageDrawable.alpha = 255 imageDrawable.alpha = 255

View File

@ -16,14 +16,13 @@
package im.vector.riotx.features.home.room.detail package im.vector.riotx.features.home.room.detail
import androidx.annotation.StringRes
import im.vector.riotx.features.command.Command import im.vector.riotx.features.command.Command
sealed class SendMessageResult { sealed class SendMessageResult {
object MessageSent : SendMessageResult() object MessageSent : SendMessageResult()
class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult()
class SlashCommandUnknown(val command: String) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult()
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() object SlashCommandHandled : SendMessageResult()
object SlashCommandResultOk : SendMessageResult() object SlashCommandResultOk : SendMessageResult()
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
// TODO Remove // TODO Remove

View File

@ -1,55 +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.readreceipts
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.features.home.AvatarRenderer
@EpoxyModelClass(layout = R.layout.item_display_read_receipt)
abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptItem.Holder>() {
@EpoxyAttribute var name: String? = null
@EpoxyAttribute var userId: String = ""
@EpoxyAttribute var avatarUrl: String? = null
@EpoxyAttribute var timestamp: CharSequence? = null
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
override fun bind(holder: Holder) {
avatarRenderer.render(avatarUrl, userId, name, holder.avatarView)
holder.displayNameView.text = name ?: userId
timestamp?.let {
holder.timestampView.text = it
holder.timestampView.isVisible = true
} ?: run {
holder.timestampView.isVisible = false
}
}
class Holder : VectorEpoxyHolder() {
val avatarView by bind<ImageView>(R.id.readReceiptAvatar)
val displayNameView by bind<TextView>(R.id.readReceiptName)
val timestampView by bind<TextView>(R.id.readReceiptDate)
}
}

View File

@ -1,91 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.home.room.detail.readreceipts
import android.os.Bundle
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 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
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.features.home.room.detail.timeline.action.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 javax.inject.Inject
@Parcelize
data class DisplayReadReceiptArgs(
val readReceipts: List<ReadReceiptData>
) : Parcelable
/**
* Bottom sheet displaying list of read receipts for a given event ordered by descending timestamp
*/
class DisplayReadReceiptsBottomSheet : VectorBaseBottomSheetDialogFragment() {
@Inject lateinit var epoxyController: DisplayReadReceiptsController
@BindView(R.id.bottom_sheet_display_reactions_list)
lateinit var epoxyRecyclerView: EpoxyRecyclerView
private val displayReadReceiptArgs: DisplayReadReceiptArgs by args()
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 = getString(R.string.read_receipts_list)
epoxyController.setData(displayReadReceiptArgs.readReceipts)
}
override fun invalidate() {
// we are not using state for this one as it's static
}
companion object {
fun newInstance(readReceipts: List<ReadReceiptData>): DisplayReadReceiptsBottomSheet {
val args = Bundle()
val parcelableArgs = DisplayReadReceiptArgs(
readReceipts
)
args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
return DisplayReadReceiptsBottomSheet().apply { arguments = args }
}
}
}

View File

@ -1,48 +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.readreceipts
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import javax.inject.Inject
/**
* Epoxy controller for read receipt event list
*/
class DisplayReadReceiptsController @Inject constructor(private val dateFormatter: VectorDateFormatter,
private val session: Session,
private val avatarRender: AvatarRenderer)
: TypedEpoxyController<List<ReadReceiptData>>() {
override fun buildModels(readReceipts: List<ReadReceiptData>) {
readReceipts.forEach {
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
DisplayReadReceiptItem_()
.id(it.userId)
.userId(it.userId)
.avatarUrl(it.avatarUrl)
.name(it.displayName)
.avatarRenderer(avatarRender)
.timestamp(timestamp)
.addIf(session.myUserId != it.userId, this)
}
}
}

View File

@ -27,7 +27,6 @@ import com.airbnb.epoxy.EpoxyModel
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.epoxy.LoadingItem_ import im.vector.riotx.core.epoxy.LoadingItem_
import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.UserPreferencesProvider import im.vector.riotx.core.resources.UserPreferencesProvider
@ -38,13 +37,12 @@ import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.riotx.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotx.features.home.room.detail.timeline.item.MergedHeaderItem
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.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.ImageContentRenderer
import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoContentRenderer
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import javax.inject.Inject import javax.inject.Inject
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter, class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter,
private val timelineItemFactory: TimelineItemFactory, private val timelineItemFactory: TimelineItemFactory,
private val timelineMediaSizeProvider: TimelineMediaSizeProvider, private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
@ -53,7 +51,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
userPreferencesProvider: UserPreferencesProvider userPreferencesProvider: UserPreferencesProvider
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener { ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback { interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback, UrlClickCallback {
fun onEventVisible(event: TimelineEvent) fun onEventVisible(event: TimelineEvent)
fun onRoomCreateLinkClicked(url: String) fun onRoomCreateLinkClicked(url: String)
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View) fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
@ -79,10 +77,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
fun onMemberNameClicked(informationData: MessageInformationData) fun onMemberNameClicked(informationData: MessageInformationData)
} }
interface ReadReceiptsCallback {
fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>)
}
interface UrlClickCallback { interface UrlClickCallback {
fun onUrlClicked(url: String): Boolean fun onUrlClicked(url: String): Boolean
fun onUrlLongClicked(url: String): Boolean fun onUrlLongClicked(url: String): Boolean
@ -164,7 +158,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
synchronized(modelCache) { synchronized(modelCache) {
for (i in 0 until modelCache.size) { for (i in 0 until modelCache.size) {
if (modelCache[i]?.eventId == eventIdToHighlight if (modelCache[i]?.eventId == eventIdToHighlight
|| modelCache[i]?.eventId == this.eventIdToHighlight) { || modelCache[i]?.eventId == this.eventIdToHighlight) {
modelCache[i] = null modelCache[i] = null
} }
} }
@ -225,8 +219,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
// Should be build if not cached or if cached but contains mergedHeader or formattedDay // Should be build if not cached or if cached but contains mergedHeader or formattedDay
// We then are sure we always have items up to date. // We then are sure we always have items up to date.
if (modelCache[position] == null if (modelCache[position] == null
|| modelCache[position]?.mergedHeaderModel != null || modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) { || modelCache[position]?.formattedDayModel != null) {
modelCache[position] = buildItemModels(position, currentSnapshot) modelCache[position] = buildItemModels(position, currentSnapshot)
} }
} }
@ -299,8 +293,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
// We try to find if one of the item id were used as mergeItemCollapseStates key // We try to find if one of the item id were used as mergeItemCollapseStates key
// => handle case where paginating from mergeable events and we get more // => handle case where paginating from mergeable events and we get more
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull() val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) ?: true
?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState } val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) { if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds) collapsedEventIds.addAll(mergedEventIds)

View File

@ -49,7 +49,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
lateinit var epoxyRecyclerView: EpoxyRecyclerView lateinit var epoxyRecyclerView: EpoxyRecyclerView
private val epoxyController by lazy { private val epoxyController by lazy {
ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer) ViewEditHistoryEpoxyController(requireContext(), viewModel.timelineDateFormatter, eventHtmlRenderer)
} }
override fun injectWith(screenComponent: ScreenComponent) { override fun injectWith(screenComponent: ScreenComponent) {

View File

@ -33,7 +33,7 @@ import im.vector.riotx.core.ui.list.genericFooterItem
import im.vector.riotx.core.ui.list.genericItem import im.vector.riotx.core.ui.list.genericItem
import im.vector.riotx.core.ui.list.genericItemHeader import im.vector.riotx.core.ui.list.genericItemHeader
import im.vector.riotx.core.ui.list.genericLoaderItem import im.vector.riotx.core.ui.list.genericLoaderItem
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.EventHtmlRenderer
import me.gujun.android.span.span import me.gujun.android.span.span
import name.fraser.neil.plaintext.diff_match_patch import name.fraser.neil.plaintext.diff_match_patch
@ -44,7 +44,7 @@ import java.util.*
* Epoxy controller for reaction event list * Epoxy controller for reaction event list
*/ */
class ViewEditHistoryEpoxyController(private val context: Context, class ViewEditHistoryEpoxyController(private val context: Context,
val dateFormatter: VectorDateFormatter, val timelineDateFormatter: TimelineDateFormatter,
val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController<ViewEditHistoryViewState>() { val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController<ViewEditHistoryViewState>() {
override fun buildModels(state: ViewEditHistoryViewState) { override fun buildModels(state: ViewEditHistoryViewState) {
@ -84,7 +84,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) { if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
//need to display header with day //need to display header with day
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today) val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
else dateFormatter.formatMessageDay(timelineEvent.localDateTime()) else timelineDateFormatter.formatMessageDay(timelineEvent.localDateTime())
genericItemHeader { genericItemHeader {
id(evDate.hashCode()) id(evDate.hashCode())
text(dateString) text(dateString)
@ -113,7 +113,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
when (it.operation) { when (it.operation) {
diff_match_patch.Operation.DELETE -> { diff_match_patch.Operation.DELETE -> {
span { span {
text = it.text.replace("\n"," ") text = it.text
textColor = ContextCompat.getColor(context, R.color.vector_error_color) textColor = ContextCompat.getColor(context, R.color.vector_error_color)
textDecorationLine = "line-through" textDecorationLine = "line-through"
} }
@ -136,7 +136,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
} }
genericItem { genericItem {
id(timelineEvent.eventId) id(timelineEvent.eventId)
title(dateFormatter.formatMessageHour(timelineEvent.localDateTime())) title(timelineDateFormatter.formatMessageHour(timelineEvent.localDateTime()))
description(spannedDiff ?: body) description(spannedDiff ?: body)
} }
} }

View File

@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
@ -46,7 +46,7 @@ data class ViewEditHistoryViewState(
class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
initialState: ViewEditHistoryViewState, initialState: ViewEditHistoryViewState,
val session: Session, val session: Session,
val dateFormatter: VectorDateFormatter val timelineDateFormatter: TimelineDateFormatter
) : VectorViewModel<ViewEditHistoryViewState>(initialState) { ) : VectorViewModel<ViewEditHistoryViewState>(initialState) {
private val roomId = initialState.roomId private val roomId = initialState.roomId

View File

@ -16,20 +16,16 @@
package im.vector.riotx.features.home.room.detail.timeline.action package im.vector.riotx.features.home.room.detail.timeline.action
import com.airbnb.mvrx.Async import com.airbnb.mvrx.*
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
import im.vector.matrix.rx.RxRoom import im.vector.matrix.rx.RxRoom
import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.utils.isSingleEmoji import im.vector.riotx.core.utils.isSingleEmoji
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
@ -58,13 +54,13 @@ data class ReactionInfo(
class ViewReactionViewModel @AssistedInject constructor(@Assisted class ViewReactionViewModel @AssistedInject constructor(@Assisted
initialState: DisplayReactionsViewState, initialState: DisplayReactionsViewState,
private val session: Session, private val session: Session,
private val dateFormatter: VectorDateFormatter private val timelineDateFormatter: TimelineDateFormatter
) : VectorViewModel<DisplayReactionsViewState>(initialState) { ) : VectorViewModel<DisplayReactionsViewState>(initialState) {
private val roomId = initialState.roomId private val roomId = initialState.roomId
private val eventId = initialState.eventId private val eventId = initialState.eventId
private val room = session.getRoom(roomId) private val room = session.getRoom(roomId)
?: throw IllegalStateException("Shouldn't use this ViewModel without a room") ?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
@AssistedInject.Factory @AssistedInject.Factory
interface Factory { interface Factory {
@ -104,14 +100,14 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
.fromIterable(summary.sourceEvents) .fromIterable(summary.sourceEvents)
.map { .map {
val event = room.getTimeLineEvent(it) val event = room.getTimeLineEvent(it)
?: throw RuntimeException("Your eventId is not valid") ?: throw RuntimeException("Your eventId is not valid")
val localDate = event.root.localDateTime()
ReactionInfo( ReactionInfo(
event.root.eventId!!, event.root.eventId!!,
summary.key, summary.key,
event.root.senderId ?: "", event.root.senderId ?: "",
event.getDisambiguatedDisplayName(), event.getDisambiguatedDisplayName(),
dateFormatter.formatRelativeDateTime(event.root.originServerTs) timelineDateFormatter.formatMessageHour(localDate)
) )
} }
}.toList() }.toList()

View File

@ -18,7 +18,6 @@ package im.vector.riotx.features.home.room.detail.timeline.action
import android.content.Context import android.content.Context
import android.graphics.Typeface import android.graphics.Typeface
import android.text.format.DateUtils
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Incomplete

View File

@ -28,15 +28,8 @@ import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
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.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
@ -54,19 +47,7 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotx.features.home.room.detail.timeline.item.BlankItem_ import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem_
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.media.ImageContentRenderer import im.vector.riotx.features.media.ImageContentRenderer
@ -84,7 +65,7 @@ class MessageItemFactory @Inject constructor(
private val imageContentRenderer: ImageContentRenderer, private val imageContentRenderer: ImageContentRenderer,
private val messageInformationDataFactory: MessageInformationDataFactory, private val messageInformationDataFactory: MessageInformationDataFactory,
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
private val noticeItemFactory: NoticeItemFactory) { private val userPreferencesProvider: UserPreferencesProvider) {
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
@ -103,26 +84,47 @@ class MessageItemFactory @Inject constructor(
val messageContent: MessageContent = val messageContent: MessageContent =
event.getLastMessageContent() event.getLastMessageContent()
?: //Malformed content, we should echo something on screen ?: //Malformed content, we should echo something on screen
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message)) return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
if (messageContent.relatesTo?.type == RelationType.REPLACE if (messageContent.relatesTo?.type == RelationType.REPLACE
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE || event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
) { ) {
// This is an edit event, we should it when debugging as a notice event // ignore replace event, the targeted id is already edited
return noticeItemFactory.create(event, highlight, callback) if (userPreferencesProvider.shouldShowHiddenEvents()) {
//These are just for debug to display hidden event, they should be filtered out in normal mode
val informationData = MessageInformationData(
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
time = "",
avatarUrl = event.senderAvatar(),
memberName = "",
showInformation = false
)
return NoticeItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.noticeText("{ \"type\": ${event.root.getClearType()} }")
.highlighted(highlight)
.baseCallback(callback)
} else {
return BlankItem_()
}
} }
// val all = event.root.toContent() // val all = event.root.toContent()
// val ev = all.toModel<Event>() // val ev = all.toModel<Event>()
return when (messageContent) { return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData, informationData,
highlight, highlight,
callback) callback)
is MessageTextContent -> buildTextMessageItem(messageContent, is MessageTextContent -> buildTextMessageItem(event.root.sendState,
informationData, messageContent,
highlight, informationData,
callback) highlight,
callback
)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback) is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback) is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback)
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback) is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback)
@ -142,7 +144,6 @@ class MessageItemFactory @Inject constructor(
.informationData(informationData) .informationData(informationData)
.highlighted(highlight) .highlighted(highlight)
.avatarCallback(callback) .avatarCallback(callback)
.readReceiptsCallback(callback)
.filename(messageContent.body) .filename(messageContent.body)
.iconRes(R.drawable.filetype_audio) .iconRes(R.drawable.filetype_audio)
.reactionPillCallback(callback) .reactionPillCallback(callback)
@ -157,7 +158,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }
@ -173,7 +174,6 @@ class MessageItemFactory @Inject constructor(
.avatarCallback(callback) .avatarCallback(callback)
.filename(messageContent.body) .filename(messageContent.body)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.readReceiptsCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface) .emojiTypeFace(emojiCompatFontProvider.typeface)
.iconRes(R.drawable.filetype_attachment) .iconRes(R.drawable.filetype_attachment)
.cellClickListener( .cellClickListener(
@ -182,12 +182,16 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
.clickListener( .clickListener(
DebouncedClickListener(View.OnClickListener { _ -> DebouncedClickListener(View.OnClickListener { _ ->
callback?.onFileMessageClicked(informationData.eventId, messageContent) callback?.onFileMessageClicked(informationData.eventId, messageContent)
})) }))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
}
} }
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? { private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? {
@ -204,6 +208,7 @@ class MessageItemFactory @Inject constructor(
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
val data = ImageContentRenderer.Data( val data = ImageContentRenderer.Data(
eventId = informationData.eventId,
filename = messageContent.body, filename = messageContent.body,
url = messageContent.getFileUrl(), url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
@ -225,7 +230,6 @@ class MessageItemFactory @Inject constructor(
.avatarCallback(callback) .avatarCallback(callback)
.mediaData(data) .mediaData(data)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.readReceiptsCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface) .emojiTypeFace(emojiCompatFontProvider.typeface)
.clickListener( .clickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
@ -237,7 +241,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }
@ -248,9 +252,10 @@ class MessageItemFactory @Inject constructor(
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
val thumbnailData = ImageContentRenderer.Data( val thumbnailData = ImageContentRenderer.Data(
eventId = informationData.eventId,
filename = messageContent.body, filename = messageContent.body,
url = messageContent.videoInfo?.thumbnailFile?.url url = messageContent.videoInfo?.thumbnailFile?.url
?: messageContent.videoInfo?.thumbnailUrl, ?: messageContent.videoInfo?.thumbnailUrl,
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(), elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
height = messageContent.videoInfo?.height, height = messageContent.videoInfo?.height,
maxHeight = maxHeight, maxHeight = maxHeight,
@ -277,7 +282,6 @@ class MessageItemFactory @Inject constructor(
.avatarCallback(callback) .avatarCallback(callback)
.mediaData(thumbnailData) .mediaData(thumbnailData)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.readReceiptsCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface) .emojiTypeFace(emojiCompatFontProvider.typeface)
.cellClickListener( .cellClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
@ -286,11 +290,12 @@ class MessageItemFactory @Inject constructor(
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) } .clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }
private fun buildTextMessageItem(messageContent: MessageTextContent, private fun buildTextMessageItem(sendState: SendState,
messageContent: MessageTextContent,
informationData: MessageInformationData, informationData: MessageInformationData,
highlight: Boolean, highlight: Boolean,
callback: TimelineEventController.Callback?): MessageTextItem? { callback: TimelineEventController.Callback?): MessageTextItem? {
@ -317,7 +322,6 @@ class MessageItemFactory @Inject constructor(
.avatarCallback(callback) .avatarCallback(callback)
.urlClickCallback(callback) .urlClickCallback(callback)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.readReceiptsCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface) .emojiTypeFace(emojiCompatFontProvider.typeface)
//click on the text //click on the text
.cellClickListener( .cellClickListener(
@ -326,7 +330,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }
@ -356,9 +360,9 @@ class MessageItemFactory @Inject constructor(
//nop //nop
} }
}, },
editStart, editStart,
editEnd, editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE) Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable return spannable
} }
@ -384,7 +388,6 @@ class MessageItemFactory @Inject constructor(
.avatarCallback(callback) .avatarCallback(callback)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.urlClickCallback(callback) .urlClickCallback(callback)
.readReceiptsCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface) .emojiTypeFace(emojiCompatFontProvider.typeface)
.memberClickListener( .memberClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
@ -396,7 +399,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }
@ -424,7 +427,6 @@ class MessageItemFactory @Inject constructor(
.highlighted(highlight) .highlighted(highlight)
.avatarCallback(callback) .avatarCallback(callback)
.reactionPillCallback(callback) .reactionPillCallback(callback)
.readReceiptsCallback(callback)
.urlClickCallback(callback) .urlClickCallback(callback)
.emojiTypeFace(emojiCompatFontProvider.typeface) .emojiTypeFace(emojiCompatFontProvider.typeface)
.cellClickListener( .cellClickListener(
@ -433,7 +435,7 @@ class MessageItemFactory @Inject constructor(
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view) return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false ?: false
} }
} }
@ -446,14 +448,13 @@ class MessageItemFactory @Inject constructor(
.informationData(informationData) .informationData(informationData)
.highlighted(highlight) .highlighted(highlight)
.avatarCallback(callback) .avatarCallback(callback)
.readReceiptsCallback(callback)
.cellClickListener( .cellClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onEventCellClicked(informationData, null, view) callback?.onEventCellClicked(informationData, null, view)
})) }))
.longClickListener { view -> .longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, null, view) return@longClickListener callback?.onEventLongClicked(informationData, null, view)
?: false ?: false
} }
} }

View File

@ -25,18 +25,23 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
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.home.room.detail.timeline.item.NoticeItem import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
import javax.inject.Inject import javax.inject.Inject
class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter, class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer) {
private val informationDataFactory: MessageInformationDataFactory) {
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
highlight: Boolean, highlight: Boolean,
callback: TimelineEventController.Callback?): NoticeItem? { callback: TimelineEventController.Callback?): NoticeItem? {
val formattedText = eventFormatter.format(event) ?: return null val formattedText = eventFormatter.format(event) ?: return null
val informationData = informationDataFactory.create(event, null) val informationData = MessageInformationData(
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
avatarUrl = event.senderAvatar(),
memberName = event.senderName(),
showInformation = false
)
return NoticeItem_() return NoticeItem_()
.avatarRenderer(avatarRenderer) .avatarRenderer(avatarRenderer)
@ -44,7 +49,6 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
.highlighted(highlight) .highlighted(highlight)
.informationData(informationData) .informationData(informationData)
.baseCallback(callback) .baseCallback(callback)
.readReceiptsCallback(callback)
} }

View File

@ -25,7 +25,6 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
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.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -34,7 +33,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
private val encryptedItemFactory: EncryptedItemFactory, private val encryptedItemFactory: EncryptedItemFactory,
private val noticeItemFactory: NoticeItemFactory, private val noticeItemFactory: NoticeItemFactory,
private val defaultItemFactory: DefaultItemFactory, private val defaultItemFactory: DefaultItemFactory,
private val roomCreateItemFactory: RoomCreateItemFactory) { private val roomCreateItemFactory: RoomCreateItemFactory,
private val avatarRenderer: AvatarRenderer) {
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
nextEvent: TimelineEvent?, nextEvent: TimelineEvent?,
@ -53,9 +53,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_HISTORY_VISIBILITY, EventType.STATE_HISTORY_VISIBILITY,
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_ANSWER, EventType.CALL_ANSWER -> noticeItemFactory.create(event, highlight, callback)
EventType.REACTION,
EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback)
// State room create // State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
// Crypto // Crypto
@ -72,9 +70,24 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
// Unhandled event types (yet) // Unhandled event types (yet)
EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STICKER -> defaultItemFactory.create(event, highlight) EventType.STICKER -> defaultItemFactory.create(event, highlight)
else -> { else -> {
Timber.v("Type ${event.root.getClearType()} not handled") //These are just for debug to display hidden event, they should be filtered out in normal mode
null val informationData = MessageInformationData(
eventId = event.root.eventId ?: "?",
senderId = event.root.senderId ?: "",
sendState = event.root.sendState,
time = "",
avatarUrl = event.senderAvatar(),
memberName = "",
showInformation = false
)
NoticeItem_()
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.noticeText("{ \"type\": ${event.root.getClearType()} }")
.highlighted(highlight)
.baseCallback(callback)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.* import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
@ -41,9 +42,6 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.MESSAGE,
EventType.REACTION,
EventType.REDACTION -> formatDebug(timelineEvent.root)
else -> { else -> {
Timber.v("Type $type not handled by this formatter") Timber.v("Type $type not handled by this formatter")
null null
@ -68,10 +66,6 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
} }
} }
private fun formatDebug(event: Event): CharSequence? {
return "{ \"type\": ${event.getClearType()} }"
}
private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? { private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
return if (!TextUtils.isEmpty(content.name)) { return if (!TextUtils.isEmpty(content.name)) {
@ -96,7 +90,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
?: return null ?: return null
val formattedVisibility = when (historyVisibility) { val formattedVisibility = when (historyVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
@ -146,7 +140,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName) stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
else -> else ->
stringProvider.getString(R.string.notice_display_name_changed_from, stringProvider.getString(R.string.notice_display_name_changed_from,
event.senderId, prevEventContent?.displayName, eventContent?.displayName) event.senderId, prevEventContent?.displayName, eventContent?.displayName)
} }
displayText.append(displayNameText) displayText.append(displayNameText)
} }
@ -173,7 +167,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
when { when {
eventContent.thirdPartyInvite != null -> eventContent.thirdPartyInvite != null ->
stringProvider.getString(R.string.notice_room_third_party_registered_invite, stringProvider.getString(R.string.notice_room_third_party_registered_invite,
targetDisplayName, eventContent.thirdPartyInvite?.displayName) targetDisplayName, eventContent.thirdPartyInvite?.displayName)
TextUtils.equals(event.stateKey, selfUserId) -> TextUtils.equals(event.stateKey, selfUserId) ->
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName) stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
event.stateKey.isNullOrEmpty() -> event.stateKey.isNullOrEmpty() ->

View File

@ -14,18 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.core.date package im.vector.riotx.features.home.room.detail.timeline.helper
import android.content.Context
import android.text.format.DateUtils
import im.vector.riotx.core.resources.LocaleProvider import im.vector.riotx.core.resources.LocaleProvider
import org.threeten.bp.LocalDateTime import org.threeten.bp.LocalDateTime
import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.DateTimeFormatter
import javax.inject.Inject import javax.inject.Inject
class VectorDateFormatter @Inject constructor(private val context: Context, class TimelineDateFormatter @Inject constructor (private val localeProvider: LocaleProvider) {
private val localeProvider: LocaleProvider) {
private val messageHourFormatter by lazy { private val messageHourFormatter by lazy {
DateTimeFormatter.ofPattern("H:mm", localeProvider.current()) DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
@ -42,16 +39,4 @@ class VectorDateFormatter @Inject constructor(private val context: Context,
return messageDayFormatter.format(localDateTime) return messageDayFormatter.format(localDateTime)
} }
fun formatRelativeDateTime(time: Long?): String {
if (time == null) {
return ""
}
return DateUtils.getRelativeDateTimeString(context,
time,
DateUtils.DAY_IN_MILLIS,
2 * DateUtils.DAY_IN_MILLIS,
DateUtils.FORMAT_SHOW_WEEKDAY
).toString()
}
} }

View File

@ -56,8 +56,7 @@ fun TimelineEvent.isDisplayable(showHiddenEvent: Boolean): Boolean {
return false return false
} }
if (root.content.isNullOrEmpty()) { if (root.content.isNullOrEmpty()) {
//redacted events have empty content but are displayable return false
return root.unsignedData?.redactedEvent != null
} }
//Edits should be filtered out! //Edits should be filtered out!
if (EventType.MESSAGE == root.type if (EventType.MESSAGE == root.type

View File

@ -32,7 +32,6 @@ import com.airbnb.epoxy.EpoxyAttribute
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.ui.views.ReadReceiptsView
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.core.utils.DimensionUtils.dpToPx import im.vector.riotx.core.utils.DimensionUtils.dpToPx
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
@ -40,6 +39,7 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
import im.vector.riotx.features.reactions.widget.ReactionButton import im.vector.riotx.features.reactions.widget.ReactionButton
import im.vector.riotx.features.ui.getMessageTextColor import im.vector.riotx.features.ui.getMessageTextColor
abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() { abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
@EpoxyAttribute @EpoxyAttribute
@ -69,9 +69,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
@EpoxyAttribute @EpoxyAttribute
var avatarCallback: TimelineEventController.AvatarCallback? = null var avatarCallback: TimelineEventController.AvatarCallback? = null
@EpoxyAttribute
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
private val _avatarClickListener = DebouncedClickListener(View.OnClickListener { private val _avatarClickListener = DebouncedClickListener(View.OnClickListener {
avatarCallback?.onAvatarClicked(informationData) avatarCallback?.onAvatarClicked(informationData)
}) })
@ -79,9 +76,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
avatarCallback?.onMemberNameClicked(informationData) avatarCallback?.onMemberNameClicked(informationData)
}) })
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
})
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
override fun onReacted(reactionButton: ReactionButton) { override fun onReacted(reactionButton: ReactionButton) {
@ -129,8 +123,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
holder.memberNameView.setOnLongClickListener(null) holder.memberNameView.setOnLongClickListener(null)
} }
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) { if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
holder.reactionWrapper?.isVisible = false holder.reactionWrapper?.isVisible = false
} else { } else {
@ -181,7 +173,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView) val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
val memberNameView by bind<TextView>(R.id.messageMemberNameView) val memberNameView by bind<TextView>(R.id.messageMemberNameView)
val timeView by bind<TextView>(R.id.messageTimeView) val timeView by bind<TextView>(R.id.messageTimeView)
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
var reactionWrapper: ViewGroup? = null var reactionWrapper: ViewGroup? = null
var reactionFlowHelper: Flow? = null var reactionFlowHelper: Flow? = null
} }

View File

@ -32,8 +32,7 @@ data class MessageInformationData(
/*List of reactions (emoji,count,isSelected)*/ /*List of reactions (emoji,count,isSelected)*/
val orderedReactionList: List<ReactionInfoData>? = null, val orderedReactionList: List<ReactionInfoData>? = null,
val hasBeenEdited: Boolean = false, val hasBeenEdited: Boolean = false,
val hasPendingEdits: Boolean = false, val hasPendingEdits: Boolean = false
val readReceipts: List<ReadReceiptData> = emptyList()
) : Parcelable ) : Parcelable
@ -44,11 +43,3 @@ data class ReactionInfoData(
val addedByMe: Boolean, val addedByMe: Boolean,
val synced: Boolean val synced: Boolean
) : Parcelable ) : Parcelable
@Parcelize
data class ReadReceiptData(
val userId: String,
val avatarUrl: String?,
val displayName: String?,
val timestamp: Long
) : Parcelable

View File

@ -22,8 +22,6 @@ import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.ui.views.ReadReceiptsView
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer 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.TimelineEventController
@ -46,13 +44,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true
} }
@EpoxyAttribute
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
})
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.noticeTextView.text = noticeText holder.noticeTextView.text = noticeText
@ -64,7 +55,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
holder.avatarImageView holder.avatarImageView
) )
holder.view.setOnLongClickListener(longClickListener) holder.view.setOnLongClickListener(longClickListener)
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
} }
override fun getViewType() = STUB_ID override fun getViewType() = STUB_ID
@ -72,7 +62,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
class Holder : BaseHolder(STUB_ID) { class Holder : BaseHolder(STUB_ID) {
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView) val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView) val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
} }
companion object { companion object {

View File

@ -16,7 +16,6 @@
package im.vector.riotx.features.home.room.detail.timeline.util package im.vector.riotx.features.home.room.detail.timeline.util
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.EventType
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.session.room.timeline.hasBeenEdited import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
@ -24,18 +23,16 @@ import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.isSingleEmoji import im.vector.riotx.core.utils.isSingleEmoji
import im.vector.riotx.features.home.getColorFromUserId import im.vector.riotx.features.home.getColorFromUserId
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
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.home.room.detail.timeline.item.ReactionInfoData import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import me.gujun.android.span.span import me.gujun.android.span.span
import javax.inject.Inject import javax.inject.Inject
/** /**
* This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline
*/ */
class MessageInformationDataFactory @Inject constructor(private val session: Session, class MessageInformationDataFactory @Inject constructor(private val timelineDateFormatter: TimelineDateFormatter,
private val dateFormatter: VectorDateFormatter,
private val colorProvider: ColorProvider) { private val colorProvider: ColorProvider) {
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData { fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
@ -46,20 +43,21 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false ?: false
val showInformation = val showInformation =
addDaySeparator addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar || event.senderAvatar != nextEvent?.senderAvatar
|| event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName() || event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName()
|| (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED) || (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo || isNextMessageReceivedMoreThanOneHourAgo
val time = dateFormatter.formatMessageHour(date) val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar val avatarUrl = event.senderAvatar
val memberName = event.getDisambiguatedDisplayName() val memberName = event.getDisambiguatedDisplayName()
val formattedMemberName = span(memberName) { val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: "")) textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId
?: ""))
} }
return MessageInformationData( return MessageInformationData(
@ -76,16 +74,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty()) ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
}, },
hasBeenEdited = event.hasBeenEdited(), hasBeenEdited = event.hasBeenEdited(),
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false, hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false
readReceipts = event.readReceipts
.asSequence()
.filter {
it.user.userId != session.myUserId
}
.map {
ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
}
.toList()
) )
} }
} }

View File

@ -121,7 +121,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
when (newState) { when (newState) {
RecyclerView.SCROLL_STATE_IDLE -> { RecyclerView.SCROLL_STATE_IDLE -> {
createChatFabMenu.postDelayed(showFabRunnable, 250) createChatFabMenu.postDelayed(showFabRunnable, 1000)
} }
RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_DRAGGING,
RecyclerView.SCROLL_STATE_SETTLING -> { RecyclerView.SCROLL_STATE_SETTLING -> {

View File

@ -29,13 +29,13 @@ import im.vector.riotx.core.resources.DateProvider
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import me.gujun.android.span.span import me.gujun.android.span.span
import javax.inject.Inject import javax.inject.Inject
class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter, class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
private val dateFormatter: VectorDateFormatter, private val timelineDateFormatter: TimelineDateFormatter,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer) { private val avatarRenderer: AvatarRenderer) {
@ -94,7 +94,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
val currentDate = DateProvider.currentLocalDateTime() val currentDate = DateProvider.currentLocalDateTime()
val isSameDay = date.toLocalDate() == currentDate.toLocalDate() val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
latestFormattedEvent = if (latestEvent.root.isEncrypted() latestFormattedEvent = if (latestEvent.root.isEncrypted()
&& latestEvent.root.mxDecryptionResult == null) { && latestEvent.root.mxDecryptionResult == null) {
stringProvider.getString(R.string.encrypted_message) stringProvider.getString(R.string.encrypted_message)
} else if (latestEvent.root.getClearType() == EventType.MESSAGE) { } else if (latestEvent.root.getClearType() == EventType.MESSAGE) {
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
@ -117,9 +117,10 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
} }
} }
latestEventTime = if (isSameDay) { latestEventTime = if (isSameDay) {
dateFormatter.formatMessageHour(date) timelineDateFormatter.formatMessageHour(date)
} else { } else {
dateFormatter.formatMessageDay(date) //TODO: change this
timelineDateFormatter.formatMessageDay(date)
} }
} }
return RoomSummaryItem_() return RoomSummaryItem_()

View File

@ -42,6 +42,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
@Parcelize @Parcelize
data class Data( data class Data(
val eventId: String,
val filename: String, val filename: String,
val url: String?, val url: String?,
val elementToDecrypt: ElementToDecrypt?, val elementToDecrypt: ElementToDecrypt?,

View File

@ -21,6 +21,8 @@ import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@ -36,6 +38,7 @@ import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator import com.github.piasy.biv.indicator.progresspie.ProgressPieIndicator
import com.github.piasy.biv.view.GlideImageViewFactory import com.github.piasy.biv.view.GlideImageViewFactory
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideApp
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
@ -47,17 +50,20 @@ import javax.inject.Inject
class ImageMediaViewerActivity : VectorBaseActivity() { class ImageMediaViewerActivity : VectorBaseActivity() {
@Inject lateinit var imageContentRenderer: ImageContentRenderer @Inject lateinit var imageContentRenderer: ImageContentRenderer
@Inject lateinit var mediaDownloadHelper: MediaDownloadHelper
lateinit var mediaData: ImageContentRenderer.Data lateinit var mediaData: ImageContentRenderer.Data
override fun getMenuRes() = R.menu.image_media_viewer
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(im.vector.riotx.R.layout.activity_image_media_viewer) setContentView(R.layout.activity_image_media_viewer)
mediaData = intent.getParcelableExtra<ImageContentRenderer.Data>(EXTRA_MEDIA_DATA) mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA)
intent.extras.getString(EXTRA_SHARED_TRANSITION_NAME)?.let { intent.extras.getString(EXTRA_SHARED_TRANSITION_NAME)?.let {
ViewCompat.setTransitionName(imageTransitionView, it) ViewCompat.setTransitionName(imageTransitionView, it)
} }
@ -105,6 +111,29 @@ class ImageMediaViewerActivity : VectorBaseActivity() {
} }
} }
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
val downloadItem = menu.findItem(R.id.download_image)
downloadItem.isVisible = !mediaData.isLocalFile()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.download_image -> mediaDownloadHelper.checkPermissionAndDownload(
mediaData.eventId,
mediaData.filename,
mediaData.url,
mediaData.elementToDecrypt
)
}
return super.onOptionsItemSelected(item)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
mediaDownloadHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
private fun configureToolbar(toolbar: Toolbar, mediaData: ImageContentRenderer.Data) { private fun configureToolbar(toolbar: Toolbar, mediaData: ImageContentRenderer.Data) {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.apply { supportActionBar?.apply {

View File

@ -0,0 +1,84 @@
/*
* 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.media
import androidx.appcompat.app.AppCompatActivity
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
import im.vector.riotx.R
import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.utils.*
import java.io.File
import javax.inject.Inject
class MediaDownloadHelper @Inject constructor(private val activity: AppCompatActivity,
private val session: Session,
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter) {
private data class PendingData(
val id: String,
val filename: String,
val url: String,
val elementToDecrypt: ElementToDecrypt?
)
private var pendingData: PendingData? = null
fun checkPermissionAndDownload(id: String, filename: String, url: String?, elementToDecrypt: ElementToDecrypt?) {
if (url.isNullOrEmpty()) {
activity.toast(stringProvider.getString(R.string.unexpected_error))
} else if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, activity, PERMISSION_REQUEST_CODE_DOWNLOAD_FILE)) {
downloadFile(id, filename, url, elementToDecrypt)
} else {
pendingData = PendingData(id, filename, url, elementToDecrypt)
}
}
fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (allGranted(grantResults) && requestCode == PERMISSION_REQUEST_CODE_DOWNLOAD_FILE) {
pendingData?.also {
downloadFile(it.id, it.filename, it.url, it.elementToDecrypt)
}
}
}
private fun downloadFile(id: String, filename: String, url: String, elementToDecrypt: ElementToDecrypt?) {
session.downloadFile(
FileService.DownloadMode.TO_EXPORT,
id,
filename,
url,
elementToDecrypt,
object : MatrixCallback<File> {
override fun onSuccess(data: File) {
activity.toast(stringProvider.getString(R.string.downloaded_file, data.path))
}
override fun onFailure(failure: Throwable) {
activity.toast(errorFormatter.toHumanReadable(failure))
}
})
}
}

View File

@ -43,7 +43,13 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
val url: String?, val url: String?,
val elementToDecrypt: ElementToDecrypt?, val elementToDecrypt: ElementToDecrypt?,
val thumbnailMediaData: ImageContentRenderer.Data val thumbnailMediaData: ImageContentRenderer.Data
) : Parcelable ) : Parcelable {
fun isLocalFile(): Boolean {
return url != null && File(url).exists()
}
}
fun render(data: Data, fun render(data: Data,
thumbnailView: ImageView, thumbnailView: ImageView,

View File

@ -19,7 +19,10 @@ package im.vector.riotx.features.media
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import kotlinx.android.synthetic.main.activity_video_media_viewer.* import kotlinx.android.synthetic.main.activity_video_media_viewer.*
@ -30,6 +33,10 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
@Inject lateinit var imageContentRenderer: ImageContentRenderer @Inject lateinit var imageContentRenderer: ImageContentRenderer
@Inject lateinit var videoContentRenderer: VideoContentRenderer @Inject lateinit var videoContentRenderer: VideoContentRenderer
@Inject lateinit var mediaDownloadHelper: MediaDownloadHelper
lateinit var mediaData: VideoContentRenderer.Data
override fun getMenuRes() = R.menu.video_media_viewer
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -37,9 +44,12 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(im.vector.riotx.R.layout.activity_video_media_viewer) setContentView(R.layout.activity_video_media_viewer)
val mediaData = intent.getParcelableExtra<VideoContentRenderer.Data>(EXTRA_MEDIA_DATA) mediaData = intent.getParcelableExtra(EXTRA_MEDIA_DATA)
if (mediaData.url.isNullOrEmpty()) {
finish()
return
}
configureToolbar(videoMediaViewerToolbar, mediaData) configureToolbar(videoMediaViewerToolbar, mediaData)
imageContentRenderer.render(mediaData.thumbnailMediaData, ImageContentRenderer.Mode.FULL_SIZE, videoMediaViewerThumbnailView) imageContentRenderer.render(mediaData.thumbnailMediaData, ImageContentRenderer.Mode.FULL_SIZE, videoMediaViewerThumbnailView)
videoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerLoading, videoMediaViewerVideoView, videoMediaViewerErrorView) videoContentRenderer.render(mediaData, videoMediaViewerThumbnailView, videoMediaViewerLoading, videoMediaViewerVideoView, videoMediaViewerErrorView)
@ -54,6 +64,28 @@ class VideoMediaViewerActivity : VectorBaseActivity() {
} }
} }
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
val downloadItem = menu.findItem(R.id.download_video)
downloadItem.isVisible = !mediaData.isLocalFile()
return super.onPrepareOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.download_video -> mediaDownloadHelper.checkPermissionAndDownload(
mediaData.eventId,
mediaData.filename,
mediaData.url,
mediaData.elementToDecrypt
)
}
return super.onOptionsItemSelected(item)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
mediaDownloadHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
companion object { companion object {
private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA" private const val EXTRA_MEDIA_DATA = "EXTRA_MEDIA_DATA"

View File

@ -44,7 +44,6 @@ import javax.inject.Singleton
*/ */
@Singleton @Singleton
class NotificationDrawerManager @Inject constructor(private val context: Context, class NotificationDrawerManager @Inject constructor(private val context: Context,
private val vectorPreferences: VectorPreferences,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val iconLoader: IconLoader, private val iconLoader: IconLoader,
private val bitmapLoader: BitmapLoader, private val bitmapLoader: BitmapLoader,
@ -74,7 +73,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
Events might be grouped and there might not be one notification per event! Events might be grouped and there might not be one notification per event!
*/ */
fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) { fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
if (!vectorPreferences.areNotificationEnabledForDevice()) { if (!VectorPreferences.areNotificationEnabledForDevice(context)) {
Timber.i("Notification are disabled for this device") Timber.i("Notification are disabled for this device")
return return
} }
@ -327,13 +326,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
globalLastMessageTimestamp = lastMessageTimestamp globalLastMessageTimestamp = lastMessageTimestamp
} }
NotificationUtils.buildMessagesListNotification(context, NotificationUtils.buildMessagesListNotification(context, style, roomEventGroupInfo, largeBitmap, lastMessageTimestamp, myUserDisplayName)
vectorPreferences,
style,
roomEventGroupInfo,
largeBitmap,
lastMessageTimestamp,
myUserDisplayName)
?.let { ?.let {
//is there an id for this room? //is there an id for this room?
notifications.add(it) notifications.add(it)
@ -351,7 +344,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
for (event in simpleEvents) { for (event in simpleEvents) {
//We build a simple event //We build a simple event
if (firstTime || !event.hasBeenDisplayed) { if (firstTime || !event.hasBeenDisplayed) {
NotificationUtils.buildSimpleEventNotification(context, vectorPreferences, event, null, session.myUserId)?.let { NotificationUtils.buildSimpleEventNotification(context, event, null, session.myUserId)?.let {
notifications.add(it) notifications.add(it)
NotificationUtils.showNotificationMessage(context, event.eventId, ROOM_EVENT_NOTIFICATION_ID, it) NotificationUtils.showNotificationMessage(context, event.eventId, ROOM_EVENT_NOTIFICATION_ID, it)
event.hasBeenDisplayed = true //we can consider it as displayed event.hasBeenDisplayed = true //we can consider it as displayed
@ -390,7 +383,6 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
NotificationUtils.buildSummaryListNotification( NotificationUtils.buildSummaryListNotification(
context, context,
vectorPreferences,
summaryInboxStyle, summaryInboxStyle,
sumTitle, sumTitle,
noisy = hasNewEvent && summaryIsNoisy, noisy = hasNewEvent && summaryIsNoisy,

View File

@ -367,7 +367,6 @@ object NotificationUtils {
* Build a notification for a Room * Build a notification for a Room
*/ */
fun buildMessagesListNotification(context: Context, fun buildMessagesListNotification(context: Context,
vectorPreferences: VectorPreferences,
messageStyle: NotificationCompat.MessagingStyle, messageStyle: NotificationCompat.MessagingStyle,
roomInfo: RoomEventGroupInfo, roomInfo: RoomEventGroupInfo,
largeIcon: Bitmap?, largeIcon: Bitmap?,
@ -421,7 +420,7 @@ object NotificationUtils {
priority = NotificationCompat.PRIORITY_DEFAULT priority = NotificationCompat.PRIORITY_DEFAULT
if (roomInfo.shouldBing) { if (roomInfo.shouldBing) {
//Compat //Compat
vectorPreferences.getNotificationRingTone()?.let { VectorPreferences.getNotificationRingTone(context)?.let {
setSound(it) setSound(it)
} }
setLights(accentColor, 500, 500) setLights(accentColor, 500, 500)
@ -477,11 +476,7 @@ object NotificationUtils {
} }
fun buildSimpleEventNotification(context: Context, fun buildSimpleEventNotification(context: Context, simpleNotifiableEvent: NotifiableEvent, largeIcon: Bitmap?, matrixId: String): Notification? {
vectorPreferences: VectorPreferences,
simpleNotifiableEvent: NotifiableEvent,
largeIcon: Bitmap?,
matrixId: String): Notification? {
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color) val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
// Build the pending intent for when the notification is clicked // Build the pending intent for when the notification is clicked
val smallIcon = R.drawable.ic_status_bar val smallIcon = R.drawable.ic_status_bar
@ -539,7 +534,7 @@ object NotificationUtils {
if (simpleNotifiableEvent.noisy) { if (simpleNotifiableEvent.noisy) {
//Compat //Compat
priority = NotificationCompat.PRIORITY_DEFAULT priority = NotificationCompat.PRIORITY_DEFAULT
vectorPreferences.getNotificationRingTone()?.let { VectorPreferences.getNotificationRingTone(context)?.let {
setSound(it) setSound(it)
} }
setLights(accentColor, 500, 500) setLights(accentColor, 500, 500)
@ -611,7 +606,6 @@ object NotificationUtils {
* Build the summary notification * Build the summary notification
*/ */
fun buildSummaryListNotification(context: Context, fun buildSummaryListNotification(context: Context,
vectorPreferences: VectorPreferences,
style: NotificationCompat.InboxStyle, style: NotificationCompat.InboxStyle,
compatSummary: String, compatSummary: String,
noisy: Boolean, noisy: Boolean,
@ -636,7 +630,7 @@ object NotificationUtils {
if (noisy) { if (noisy) {
//Compat //Compat
priority = NotificationCompat.PRIORITY_DEFAULT priority = NotificationCompat.PRIORITY_DEFAULT
vectorPreferences.getNotificationRingTone()?.let { VectorPreferences.getNotificationRingTone(context)?.let {
setSound(it) setSound(it)
} }
setLights(accentColor, 500, 500) setLights(accentColor, 500, 500)

View File

@ -35,7 +35,7 @@ import im.vector.riotx.core.extensions.toOnOff
import im.vector.riotx.core.utils.getDeviceLocale import im.vector.riotx.core.utils.getDeviceLocale
import im.vector.riotx.features.settings.VectorLocale import im.vector.riotx.features.settings.VectorLocale
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import im.vector.riotx.features.version.VersionProvider import im.vector.riotx.features.version.getVersion
import okhttp3.* import okhttp3.*
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -51,8 +51,7 @@ import javax.inject.Singleton
* BugReporter creates and sends the bug reports. * BugReporter creates and sends the bug reports.
*/ */
@Singleton @Singleton
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
private val versionProvider: VersionProvider) {
var inMultiWindowMode = false var inMultiWindowMode = false
companion object { companion object {
@ -226,7 +225,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent()) .addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
.addFormDataPart("user_id", userId) .addFormDataPart("user_id", userId)
.addFormDataPart("device_id", deviceId) .addFormDataPart("device_id", deviceId)
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false)) .addFormDataPart("version", getVersion(longFormat = true, useBuildNumber = false))
.addFormDataPart("branch_name", context.getString(R.string.git_branch_name)) .addFormDataPart("branch_name", context.getString(R.string.git_branch_name))
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion()) .addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
.addFormDataPart("olm_version", olmVersion) .addFormDataPart("olm_version", olmVersion)

View File

@ -21,8 +21,8 @@ import android.os.Build
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotx.core.resources.VersionCodeProvider import im.vector.riotx.BuildConfig
import im.vector.riotx.features.version.VersionProvider import im.vector.riotx.features.version.getVersion
import timber.log.Timber import timber.log.Timber
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
@ -30,15 +30,16 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter, class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter) : Thread.UncaughtExceptionHandler {
private val versionProvider: VersionProvider,
private val versionCodeProvider: VersionCodeProvider) : Thread.UncaughtExceptionHandler {
// key to save the crash status // key to save the crash status
companion object { companion object {
private const val PREFS_CRASH_KEY = "PREFS_CRASH_KEY" private const val PREFS_CRASH_KEY = "PREFS_CRASH_KEY"
} }
private val vectorVersion = getVersion(longFormat = true, useBuildNumber = true)
private val matrixSdkVersion = Matrix.getSdkVersion()
private var previousHandler: Thread.UncaughtExceptionHandler? = null private var previousHandler: Thread.UncaughtExceptionHandler? = null
private lateinit var context: Context private lateinit var context: Context
@ -67,9 +68,9 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
val b = StringBuilder() val b = StringBuilder()
val appName = "RiotX" // TODO Matrix.getApplicationName() val appName = "RiotX" // TODO Matrix.getApplicationName()
b.append(appName + " Build : " + versionCodeProvider.getVersionCode() + "\n") b.append(appName + " Build : " + BuildConfig.VERSION_CODE + "\n")
b.append("$appName Version : ${versionProvider.getVersion(longFormat = true, useBuildNumber = true)}\n") b.append("$appName Version : $vectorVersion\n")
b.append("SDK Version : ${Matrix.getSdkVersion()}\n") b.append("SDK Version : $matrixSdkVersion\n")
b.append("Phone : " + Build.MODEL.trim() + " (" + Build.VERSION.INCREMENTAL + " " + Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME + ")\n") b.append("Phone : " + Build.MODEL.trim() + " (" + Build.VERSION.INCREMENTAL + " " + Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME + ")\n")
b.append("Memory statuses \n") b.append("Memory statuses \n")
@ -93,6 +94,14 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
b.append("Thread: ") b.append("Thread: ")
b.append(thread.name) b.append(thread.name)
/*
val a = VectorApp.getCurrentActivity()
if (a != null) {
b.append(", Activity:")
b.append(a.localClassName)
}
*/
b.append(", Exception: ") b.append(", Exception: ")
val sw = StringWriter() val sw = StringWriter()

View File

@ -18,7 +18,6 @@ package im.vector.riotx.features.roomdirectory.roompreview
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
import com.airbnb.mvrx.args import com.airbnb.mvrx.args
@ -105,12 +104,7 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {
} }
) )
if (state.lastError == null) { roomPreviewNoPreviewError.setTextOrHide(errorFormatter.toHumanReadable(state.lastError))
roomPreviewNoPreviewError.isVisible = false
} else {
roomPreviewNoPreviewError.isVisible = true
roomPreviewNoPreviewError.text = errorFormatter.toHumanReadable(state.lastError)
}
if (state.roomJoinState == JoinState.JOINED) { if (state.roomJoinState == JoinState.JOINED) {
// Quit this screen // Quit this screen

View File

@ -31,192 +31,187 @@ import im.vector.riotx.features.themes.ThemeUtils
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.util.* import java.util.*
import javax.inject.Inject
class VectorPreferences @Inject constructor(private val context: Context) { object VectorPreferences {
companion object { const val SETTINGS_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY = "SETTINGS_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY_2"
const val SETTINGS_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY = "SETTINGS_MESSAGES_SENT_BY_BOT_PREFERENCE_KEY_2" const val SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY = "SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY"
const val SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY = "SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY" const val SETTINGS_VERSION_PREFERENCE_KEY = "SETTINGS_VERSION_PREFERENCE_KEY"
const val SETTINGS_VERSION_PREFERENCE_KEY = "SETTINGS_VERSION_PREFERENCE_KEY" const val SETTINGS_SDK_VERSION_PREFERENCE_KEY = "SETTINGS_SDK_VERSION_PREFERENCE_KEY"
const val SETTINGS_SDK_VERSION_PREFERENCE_KEY = "SETTINGS_SDK_VERSION_PREFERENCE_KEY" const val SETTINGS_OLM_VERSION_PREFERENCE_KEY = "SETTINGS_OLM_VERSION_PREFERENCE_KEY"
const val SETTINGS_OLM_VERSION_PREFERENCE_KEY = "SETTINGS_OLM_VERSION_PREFERENCE_KEY" const val SETTINGS_LOGGED_IN_PREFERENCE_KEY = "SETTINGS_LOGGED_IN_PREFERENCE_KEY"
const val SETTINGS_LOGGED_IN_PREFERENCE_KEY = "SETTINGS_LOGGED_IN_PREFERENCE_KEY" const val SETTINGS_HOME_SERVER_PREFERENCE_KEY = "SETTINGS_HOME_SERVER_PREFERENCE_KEY"
const val SETTINGS_HOME_SERVER_PREFERENCE_KEY = "SETTINGS_HOME_SERVER_PREFERENCE_KEY" const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY"
const val SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY = "SETTINGS_IDENTITY_SERVER_PREFERENCE_KEY" const val SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY"
const val SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY" const val SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY"
const val SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY"
const val SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY" const val SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY"
const val SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY" const val SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY"
const val SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY" const val SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY"
const val SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY" const val SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY = "SETTINGS_OTHER_THIRD_PARTY_NOTICES_PREFERENCE_KEY"
const val SETTINGS_COPYRIGHT_PREFERENCE_KEY = "SETTINGS_COPYRIGHT_PREFERENCE_KEY" const val SETTINGS_COPYRIGHT_PREFERENCE_KEY = "SETTINGS_COPYRIGHT_PREFERENCE_KEY"
const val SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY" const val SETTINGS_CLEAR_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_CACHE_PREFERENCE_KEY"
const val SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY" const val SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY = "SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY"
const val SETTINGS_USER_SETTINGS_PREFERENCE_KEY = "SETTINGS_USER_SETTINGS_PREFERENCE_KEY" const val SETTINGS_USER_SETTINGS_PREFERENCE_KEY = "SETTINGS_USER_SETTINGS_PREFERENCE_KEY"
const val SETTINGS_CONTACT_PREFERENCE_KEYS = "SETTINGS_CONTACT_PREFERENCE_KEYS" const val SETTINGS_CONTACT_PREFERENCE_KEYS = "SETTINGS_CONTACT_PREFERENCE_KEYS"
const val SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY" const val SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
const val SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY" const val SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_IGNORED_USERS_PREFERENCE_KEY = "SETTINGS_IGNORED_USERS_PREFERENCE_KEY" const val SETTINGS_IGNORED_USERS_PREFERENCE_KEY = "SETTINGS_IGNORED_USERS_PREFERENCE_KEY"
const val SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY = "SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY" const val SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY = "SETTINGS_IGNORE_USERS_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY" const val SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY"
const val SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY" const val SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_LABS_PREFERENCE_KEY = "SETTINGS_LABS_PREFERENCE_KEY" const val SETTINGS_LABS_PREFERENCE_KEY = "SETTINGS_LABS_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
const val SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_DEVICES_LIST_PREFERENCE_KEY = "SETTINGS_DEVICES_LIST_PREFERENCE_KEY" const val SETTINGS_DEVICES_LIST_PREFERENCE_KEY = "SETTINGS_DEVICES_LIST_PREFERENCE_KEY"
const val SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY = "SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY" const val SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY = "SETTINGS_DEVICES_DIVIDER_PREFERENCE_KEY"
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY" const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY"
const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY" const val SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY = "SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_IS_ACTIVE_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_EXPORT_E2E_ROOM_KEYS_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_IMPORT_E2E_ROOM_KEYS_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY"
const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY" const val SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY"
const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY"
// user // user
const val SETTINGS_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_DISPLAY_NAME_PREFERENCE_KEY" const val SETTINGS_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_DISPLAY_NAME_PREFERENCE_KEY"
const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY" const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY"
// contacts // contacts
const val SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY = "SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY" const val SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY = "SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY"
// interface // interface
const val SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY = "SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY" const val SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY = "SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY"
const val SETTINGS_INTERFACE_TEXT_SIZE_KEY = "SETTINGS_INTERFACE_TEXT_SIZE_KEY" const val SETTINGS_INTERFACE_TEXT_SIZE_KEY = "SETTINGS_INTERFACE_TEXT_SIZE_KEY"
const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY" const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY"
private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY" private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY"
private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY" private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY"
private const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY" private const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY" private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY"
private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY" private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY"
private const val SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY = "SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY" private const val SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY = "SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY"
private const val SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY = "SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY" private const val SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY = "SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY"
private const val SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY" private const val SETTINGS_VIBRATE_ON_MENTION_KEY = "SETTINGS_VIBRATE_ON_MENTION_KEY"
private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER" private const val SETTINGS_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER"
// home // home
private const val SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY = "SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY" private const val SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY = "SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY"
private const val SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY = "SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY" private const val SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY = "SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY"
// flair // flair
const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY" const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY"
// notifications // notifications
const val SETTINGS_NOTIFICATIONS_KEY = "SETTINGS_NOTIFICATIONS_KEY" const val SETTINGS_NOTIFICATIONS_KEY = "SETTINGS_NOTIFICATIONS_KEY"
const val SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY" const val SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY = "SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY"
const val SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY" const val SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY = "SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY"
// public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY"; // public static final String SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY = "SETTINGS_TURN_SCREEN_ON_PREFERENCE_KEY";
const val SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY" const val SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY"
const val SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY" const val SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_NOISY_NOTIFICATION_PREFERENCE_KEY"
const val SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY" const val SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY = "SETTINGS_SYSTEM_SILENT_NOTIFICATION_PREFERENCE_KEY"
const val SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY" const val SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY"
const val SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY" const val SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY"
const val SETTINGS_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY_2" const val SETTINGS_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_CONTAINING_MY_DISPLAY_NAME_PREFERENCE_KEY_2"
const val SETTINGS_CONTAINING_MY_USER_NAME_PREFERENCE_KEY = "SETTINGS_CONTAINING_MY_USER_NAME_PREFERENCE_KEY_2" const val SETTINGS_CONTAINING_MY_USER_NAME_PREFERENCE_KEY = "SETTINGS_CONTAINING_MY_USER_NAME_PREFERENCE_KEY_2"
const val SETTINGS_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY = "SETTINGS_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY_2" const val SETTINGS_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY = "SETTINGS_MESSAGES_IN_ONE_TO_ONE_PREFERENCE_KEY_2"
const val SETTINGS_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY = "SETTINGS_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY_2" const val SETTINGS_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY = "SETTINGS_MESSAGES_IN_GROUP_CHAT_PREFERENCE_KEY_2"
const val SETTINGS_INVITED_TO_ROOM_PREFERENCE_KEY = "SETTINGS_INVITED_TO_ROOM_PREFERENCE_KEY_2" const val SETTINGS_INVITED_TO_ROOM_PREFERENCE_KEY = "SETTINGS_INVITED_TO_ROOM_PREFERENCE_KEY_2"
const val SETTINGS_CALL_INVITATIONS_PREFERENCE_KEY = "SETTINGS_CALL_INVITATIONS_PREFERENCE_KEY_2" const val SETTINGS_CALL_INVITATIONS_PREFERENCE_KEY = "SETTINGS_CALL_INVITATIONS_PREFERENCE_KEY_2"
// media // media
private const val SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY = "SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY" private const val SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY = "SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY"
private const val SETTINGS_DEFAULT_MEDIA_SOURCE_KEY = "SETTINGS_DEFAULT_MEDIA_SOURCE_KEY" private const val SETTINGS_DEFAULT_MEDIA_SOURCE_KEY = "SETTINGS_DEFAULT_MEDIA_SOURCE_KEY"
private const val SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY = "SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY" private const val SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY = "SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY"
private const val SETTINGS_PLAY_SHUTTER_SOUND_KEY = "SETTINGS_PLAY_SHUTTER_SOUND_KEY" private const val SETTINGS_PLAY_SHUTTER_SOUND_KEY = "SETTINGS_PLAY_SHUTTER_SOUND_KEY"
// background sync // background sync
const val SETTINGS_START_ON_BOOT_PREFERENCE_KEY = "SETTINGS_START_ON_BOOT_PREFERENCE_KEY" const val SETTINGS_START_ON_BOOT_PREFERENCE_KEY = "SETTINGS_START_ON_BOOT_PREFERENCE_KEY"
const val SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY = "SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY" const val SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY = "SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY"
const val SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY = "SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY" const val SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY = "SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY"
const val SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY = "SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY" const val SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY = "SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY"
// Calls // Calls
const val SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY" const val SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY"
const val SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY" const val SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY = "SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY"
// labs // labs
const val SETTINGS_LAZY_LOADING_PREFERENCE_KEY = "SETTINGS_LAZY_LOADING_PREFERENCE_KEY" const val SETTINGS_LAZY_LOADING_PREFERENCE_KEY = "SETTINGS_LAZY_LOADING_PREFERENCE_KEY"
const val SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY = "SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY" const val SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY = "SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY"
const val SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY = "SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY" const val SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY = "SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY"
private const val SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY = "SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY" private const val SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY = "SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY"
private const val SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY = "SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY" private const val SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY = "SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY"
private const val SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY = "SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY" private const val SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY = "SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY"
private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY" private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
// analytics // analytics
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY" const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY" const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
// other // other
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY" const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
private const val SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY" private const val SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY"
private const val DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY = "DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY" private const val DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY = "DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY"
private const val DID_MIGRATE_TO_NOTIFICATION_REWORK = "DID_MIGRATE_TO_NOTIFICATION_REWORK" private const val DID_MIGRATE_TO_NOTIFICATION_REWORK = "DID_MIGRATE_TO_NOTIFICATION_REWORK"
private const val DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY = "DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY" private const val DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY = "DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY"
const val SETTINGS_DEACTIVATE_ACCOUNT_KEY = "SETTINGS_DEACTIVATE_ACCOUNT_KEY" const val SETTINGS_DEACTIVATE_ACCOUNT_KEY = "SETTINGS_DEACTIVATE_ACCOUNT_KEY"
private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY" private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY"
private const val MEDIA_SAVING_3_DAYS = 0 private const val MEDIA_SAVING_3_DAYS = 0
private const val MEDIA_SAVING_1_WEEK = 1 private const val MEDIA_SAVING_1_WEEK = 1
private const val MEDIA_SAVING_1_MONTH = 2 private const val MEDIA_SAVING_1_MONTH = 2
private const val MEDIA_SAVING_FOREVER = 3 private const val MEDIA_SAVING_FOREVER = 3
// some preferences keys must be kept after a logout // some preferences keys must be kept after a logout
private val mKeysToKeepAfterLogout = Arrays.asList( private val mKeysToKeepAfterLogout = Arrays.asList(
SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY, SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY,
SETTINGS_DEFAULT_MEDIA_SOURCE_KEY, SETTINGS_DEFAULT_MEDIA_SOURCE_KEY,
SETTINGS_PLAY_SHUTTER_SOUND_KEY, SETTINGS_PLAY_SHUTTER_SOUND_KEY,
SETTINGS_SEND_TYPING_NOTIF_KEY, SETTINGS_SEND_TYPING_NOTIF_KEY,
SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY, SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY,
SETTINGS_12_24_TIMESTAMPS_KEY, SETTINGS_12_24_TIMESTAMPS_KEY,
SETTINGS_SHOW_READ_RECEIPTS_KEY, SETTINGS_SHOW_READ_RECEIPTS_KEY,
SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY, SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY,
SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY, SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY,
SETTINGS_MEDIA_SAVING_PERIOD_KEY, SETTINGS_MEDIA_SAVING_PERIOD_KEY,
SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY,
SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY, SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY,
SETTINGS_SEND_MESSAGE_WITH_ENTER, SETTINGS_SEND_MESSAGE_WITH_ENTER,
SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY, SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY,
SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY, SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY,
// Do not keep SETTINGS_LAZY_LOADING_PREFERENCE_KEY because the user may log in on a server which does not support lazy loading // Do not keep SETTINGS_LAZY_LOADING_PREFERENCE_KEY because the user may log in on a server which does not support lazy loading
SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY, SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY,
SETTINGS_START_ON_BOOT_PREFERENCE_KEY, SETTINGS_START_ON_BOOT_PREFERENCE_KEY,
SETTINGS_INTERFACE_TEXT_SIZE_KEY, SETTINGS_INTERFACE_TEXT_SIZE_KEY,
SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY, SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY,
SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY,
SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY, SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY,
SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY, SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY,
SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY, SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY,
SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY, SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY,
SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY, SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY,
SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY, SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY,
SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY,
SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY,
SETTINGS_USE_RAGE_SHAKE_KEY SETTINGS_USE_RAGE_SHAKE_KEY
) )
}
private val defaultPrefs = PreferenceManager.getDefaultSharedPreferences(context)
/** /**
* Clear the preferences. * Clear the preferences.
* *
* @param context the context * @param context the context
*/ */
fun clearPreferences() { fun clearPreferences(context: Context) {
val keysToKeep = HashSet(mKeysToKeepAfterLogout) val keysToKeep = HashSet(mKeysToKeepAfterLogout)
// home server urls // home server urls
@ -226,35 +221,37 @@ class VectorPreferences @Inject constructor(private val context: Context) {
// theme // theme
keysToKeep.add(ThemeUtils.APPLICATION_THEME_KEY) keysToKeep.add(ThemeUtils.APPLICATION_THEME_KEY)
// get all the existing keys val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val keys = defaultPrefs.all.keys preferences.edit {
// get all the existing keys
val keys = preferences.all.keys
// remove the one to keep
// remove the one to keep keys.removeAll(keysToKeep)
keys.removeAll(keysToKeep)
defaultPrefs.edit {
for (key in keys) { for (key in keys) {
remove(key) remove(key)
} }
} }
} }
fun areNotificationEnabledForDevice(): Boolean { fun areNotificationEnabledForDevice(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, true)
} }
fun setNotificationEnabledForDevice(enabled: Boolean?) { fun setNotificationEnabledForDevice(context: Context, enabled: Boolean?) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, enabled!!) .edit {
} putBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, enabled!!)
}
} }
fun shouldShowHiddenEvents(): Boolean { fun shouldShowHiddenEvents(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false)
} }
fun swipeToReplyIsEnabled(): Boolean { fun swipeToReplyIsEnabled(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true)
} }
/** /**
@ -263,8 +260,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if it was already requested * @return true if it was already requested
*/ */
fun didAskUserToIgnoreBatteryOptimizations(): Boolean { fun didAskUserToIgnoreBatteryOptimizations(context: Context): Boolean {
return defaultPrefs.getBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, false)
} }
/** /**
@ -272,20 +269,22 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* *
* @param context the context * @param context the context
*/ */
fun setDidAskUserToIgnoreBatteryOptimizations() { fun setDidAskUserToIgnoreBatteryOptimizations(context: Context) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, true) .edit {
} putBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, true)
}
} }
fun didMigrateToNotificationRework(): Boolean { fun didMigrateToNotificationRework(context: Context): Boolean {
return defaultPrefs.getBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, false)
} }
fun setDidMigrateToNotificationRework() { fun setDidMigrateToNotificationRework(context: Context) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, true) .edit {
} putBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, true)
}
} }
/** /**
@ -294,8 +293,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the time must be displayed in 12h format * @return true if the time must be displayed in 12h format
*/ */
fun displayTimeIn12hFormat(): Boolean { fun displayTimeIn12hFormat(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_12_24_TIMESTAMPS_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_12_24_TIMESTAMPS_KEY, false)
} }
/** /**
@ -304,8 +303,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the join and leave membership events should be shown in the messages list * @return true if the join and leave membership events should be shown in the messages list
*/ */
fun showJoinLeaveMessages(): Boolean { fun showJoinLeaveMessages(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY, true)
} }
/** /**
@ -314,8 +313,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true true if the avatar and display name events should be shown in the messages list. * @return true true if the avatar and display name events should be shown in the messages list.
*/ */
fun showAvatarDisplayNameChangeMessages(): Boolean { fun showAvatarDisplayNameChangeMessages(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY, true)
} }
/** /**
@ -324,8 +323,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to use the native camera app to record video or take photo. * @return true to use the native camera app to record video or take photo.
*/ */
fun useNativeCamera(): Boolean { fun useNativeCamera(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY, false)
} }
/** /**
@ -334,8 +333,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the send voice feature is enabled. * @return true if the send voice feature is enabled.
*/ */
fun isSendVoiceFeatureEnabled(): Boolean { fun isSendVoiceFeatureEnabled(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY, false)
} }
/** /**
@ -344,8 +343,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the selected compression level * @return the selected compression level
*/ */
fun getSelectedDefaultMediaCompressionLevel(): Int { fun getSelectedDefaultMediaCompressionLevel(context: Context): Int {
return Integer.parseInt(defaultPrefs.getString(SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY, "0")!!) return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString(SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY, "0")!!)
} }
/** /**
@ -354,8 +353,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the selected media source * @return the selected media source
*/ */
fun getSelectedDefaultMediaSource(): Int { fun getSelectedDefaultMediaSource(context: Context): Int {
return Integer.parseInt(defaultPrefs.getString(SETTINGS_DEFAULT_MEDIA_SOURCE_KEY, "0")!!) return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString(SETTINGS_DEFAULT_MEDIA_SOURCE_KEY, "0")!!)
} }
/** /**
@ -364,8 +363,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if shutter sound should play * @return true if shutter sound should play
*/ */
fun useShutterSound(): Boolean { fun useShutterSound(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true)
} }
/** /**
@ -374,8 +373,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param uri the new notification ringtone, or null for no RingTone * @param uri the new notification ringtone, or null for no RingTone
*/ */
fun setNotificationRingTone(uri: Uri?) { fun setNotificationRingTone(context: Context, uri: Uri?) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context).edit {
var value = "" var value = ""
if (null != uri) { if (null != uri) {
@ -399,8 +399,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the selected ring tone or null for no RingTone * @return the selected ring tone or null for no RingTone
*/ */
fun getNotificationRingTone(): Uri? { fun getNotificationRingTone(context: Context): Uri? {
val url = defaultPrefs.getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null) val url = PreferenceManager.getDefaultSharedPreferences(context).getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null)
// the user selects "None" // the user selects "None"
if (TextUtils.equals(url, "")) { if (TextUtils.equals(url, "")) {
@ -433,8 +433,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the filename or null if "None" is selected * @return the filename or null if "None" is selected
*/ */
fun getNotificationRingToneName(): String? { fun getNotificationRingToneName(context: Context): String? {
val toneUri = getNotificationRingTone() ?: return null val toneUri = getNotificationRingTone(context) ?: return null
var name: String? = null var name: String? = null
@ -467,10 +467,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param newValue true to enable lazy loading, false to disable it * @param newValue true to enable lazy loading, false to disable it
*/ */
fun setUseLazyLoading(newValue: Boolean) { fun setUseLazyLoading(context: Context, newValue: Boolean) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, newValue) .edit {
} putBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, newValue)
}
} }
/** /**
@ -479,8 +480,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the lazy loading of room members is enabled * @return true if the lazy loading of room members is enabled
*/ */
fun useLazyLoading(): Boolean { fun useLazyLoading(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, false)
} }
/** /**
@ -488,10 +489,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* *
* @param context the context * @param context the context
*/ */
fun setUserRefuseLazyLoading() { fun setUserRefuseLazyLoading(context: Context) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, true) .edit {
} putBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, true)
}
} }
/** /**
@ -500,8 +502,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the user has explicitly refuse the lazy loading of room members * @return true if the user has explicitly refuse the lazy loading of room members
*/ */
fun hasUserRefusedLazyLoading(): Boolean { fun hasUserRefusedLazyLoading(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, false)
} }
/** /**
@ -510,8 +512,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the data save mode is enabled * @return true if the data save mode is enabled
*/ */
fun useDataSaveMode(): Boolean { fun useDataSaveMode(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY, false)
} }
/** /**
@ -520,8 +522,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the conference call must be done with jitsi. * @return true if the conference call must be done with jitsi.
*/ */
fun useJitsiConfCall(): Boolean { fun useJitsiConfCall(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY, true)
} }
/** /**
@ -530,8 +532,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the application must be started on boot * @return true if the application must be started on boot
*/ */
fun autoStartOnBoot(): Boolean { fun autoStartOnBoot(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, true)
} }
/** /**
@ -540,10 +542,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param value true to start the application on boot * @param value true to start the application on boot
*/ */
fun setAutoStartOnBoot(value: Boolean) { fun setAutoStartOnBoot(context: Context, value: Boolean) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, value) .edit {
} putBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, value)
}
} }
/** /**
@ -552,8 +555,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the selected period * @return the selected period
*/ */
fun getSelectedMediasSavingPeriod(): Int { fun getSelectedMediasSavingPeriod(context: Context): Int {
return defaultPrefs.getInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, MEDIA_SAVING_1_WEEK) return PreferenceManager.getDefaultSharedPreferences(context).getInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, MEDIA_SAVING_1_WEEK)
} }
/** /**
@ -562,10 +565,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param index the selected period index * @param index the selected period index
*/ */
fun setSelectedMediasSavingPeriod(index: Int) { fun setSelectedMediasSavingPeriod(context: Context, index: Int) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, index) .edit {
} putInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, index)
}
} }
/** /**
@ -574,8 +578,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the min last access time (in seconds) * @return the min last access time (in seconds)
*/ */
fun getMinMediasLastAccessTime(): Long { fun getMinMediasLastAccessTime(context: Context): Long {
val selection = getSelectedMediasSavingPeriod() val selection = getSelectedMediasSavingPeriod(context)
when (selection) { when (selection) {
MEDIA_SAVING_3_DAYS -> return System.currentTimeMillis() / 1000 - 3 * 24 * 60 * 60 MEDIA_SAVING_3_DAYS -> return System.currentTimeMillis() / 1000 - 3 * 24 * 60 * 60
@ -593,8 +597,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return the selected period * @return the selected period
*/ */
fun getSelectedMediasSavingPeriodString(): String { fun getSelectedMediasSavingPeriodString(context: Context): String {
val selection = getSelectedMediasSavingPeriod() val selection = getSelectedMediasSavingPeriod(context)
when (selection) { when (selection) {
MEDIA_SAVING_3_DAYS -> return context.getString(R.string.media_saving_period_3_days) MEDIA_SAVING_3_DAYS -> return context.getString(R.string.media_saving_period_3_days)
@ -608,7 +612,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
/** /**
* Fix some migration issues * Fix some migration issues
*/ */
fun fixMigrationIssues() { fun fixMigrationIssues(context: Context) {
// Nothing to do for the moment // Nothing to do for the moment
} }
@ -618,8 +622,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the markdown is enabled * @return true if the markdown is enabled
*/ */
fun isMarkdownEnabled(): Boolean { fun isMarkdownEnabled(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, true)
} }
/** /**
@ -628,10 +632,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param isEnabled true to enable the markdown * @param isEnabled true to enable the markdown
*/ */
fun setMarkdownEnabled(isEnabled: Boolean) { fun setMarkdownEnabled(context: Context, isEnabled: Boolean) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, isEnabled) .edit {
} putBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, isEnabled)
}
} }
/** /**
@ -640,8 +645,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the read receipts should be shown * @return true if the read receipts should be shown
*/ */
fun showReadReceipts(): Boolean { fun showReadReceipts(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SHOW_READ_RECEIPTS_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SHOW_READ_RECEIPTS_KEY, true)
} }
/** /**
@ -650,8 +655,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the message timestamps must be always shown * @return true if the message timestamps must be always shown
*/ */
fun alwaysShowTimeStamps(): Boolean { fun alwaysShowTimeStamps(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY, false)
} }
/** /**
@ -660,8 +665,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to send the typing notifs * @return true to send the typing notifs
*/ */
fun sendTypingNotifs(): Boolean { fun sendTypingNotifs(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SEND_TYPING_NOTIF_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SEND_TYPING_NOTIF_KEY, true)
} }
/** /**
@ -670,8 +675,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to move the missed notifications to the left side * @return true to move the missed notifications to the left side
*/ */
fun pinMissedNotifications(): Boolean { fun pinMissedNotifications(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY, true)
} }
/** /**
@ -680,8 +685,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to move the unread room to the left side * @return true to move the unread room to the left side
*/ */
fun pinUnreadMessages(): Boolean { fun pinUnreadMessages(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY, true)
} }
/** /**
@ -690,8 +695,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true * @return true
*/ */
fun vibrateWhenMentioning(): Boolean { fun vibrateWhenMentioning(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_VIBRATE_ON_MENTION_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_VIBRATE_ON_MENTION_KEY, false)
} }
/** /**
@ -700,8 +705,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if a dialog has been displayed to ask to use the analytics tracking * @return true if a dialog has been displayed to ask to use the analytics tracking
*/ */
fun didAskToUseAnalytics(): Boolean { fun didAskToUseAnalytics(context: Context): Boolean {
return defaultPrefs.getBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, false)
} }
/** /**
@ -709,10 +714,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* *
* @param context the context * @param context the context
*/ */
fun setDidAskToUseAnalytics() { fun setDidAskToUseAnalytics(context: Context) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, true) .edit {
} putBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, true)
}
} }
/** /**
@ -721,8 +727,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the analytics tracking is authorized * @return true if the analytics tracking is authorized
*/ */
fun useAnalytics(): Boolean { fun useAnalytics(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_USE_ANALYTICS_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_ANALYTICS_KEY, false)
} }
/** /**
@ -731,10 +737,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param useAnalytics true to enable the analytics tracking * @param useAnalytics true to enable the analytics tracking
*/ */
fun setUseAnalytics(useAnalytics: Boolean) { fun setUseAnalytics(context: Context, useAnalytics: Boolean) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_USE_ANALYTICS_KEY, useAnalytics) .edit {
} putBoolean(SETTINGS_USE_ANALYTICS_KEY, useAnalytics)
}
} }
/** /**
@ -743,8 +750,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to preview media * @return true to preview media
*/ */
fun previewMediaWhenSending(): Boolean { fun previewMediaWhenSending(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY, false)
} }
/** /**
@ -753,8 +760,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to send message with enter * @return true to send message with enter
*/ */
fun sendMessageWithEnter(): Boolean { fun sendMessageWithEnter(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SEND_MESSAGE_WITH_ENTER, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SEND_MESSAGE_WITH_ENTER, false)
} }
/** /**
@ -763,8 +770,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true if the rage shake is used * @return true if the rage shake is used
*/ */
fun useRageshake(): Boolean { fun useRageshake(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true)
} }
/** /**
@ -773,10 +780,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @param isEnabled true to enable the rage shake * @param isEnabled true to enable the rage shake
*/ */
fun setUseRageshake(isEnabled: Boolean) { fun setUseRageshake(context: Context, isEnabled: Boolean) {
defaultPrefs.edit { PreferenceManager.getDefaultSharedPreferences(context)
putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled) .edit {
} putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled)
}
} }
/** /**
@ -785,7 +793,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
* @param context the context * @param context the context
* @return true to display all the events even the redacted ones. * @return true to display all the events even the redacted ones.
*/ */
fun displayAllEvents(): Boolean { fun displayAllEvents(context: Context): Boolean {
return defaultPrefs.getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false) return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false)
} }
} }

View File

@ -24,13 +24,11 @@ import androidx.core.content.edit
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.withArgs import im.vector.riotx.core.extensions.withArgs
import im.vector.riotx.core.preference.BingRule import im.vector.riotx.core.preference.BingRule
import im.vector.riotx.core.preference.BingRulePreference import im.vector.riotx.core.preference.BingRulePreference
import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.notifications.supportNotificationChannels import im.vector.riotx.features.notifications.supportNotificationChannels
import javax.inject.Inject
class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() { class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() {
@ -47,13 +45,6 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseF
override val preferenceXmlRes = R.xml.vector_settings_notification_advanced_preferences override val preferenceXmlRes = R.xml.vector_settings_notification_advanced_preferences
@Inject lateinit var vectorPreferences: VectorPreferences
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun bindPref() { override fun bindPref() {
val callNotificationsSystemOptions = findPreference(VectorPreferences.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY) val callNotificationsSystemOptions = findPreference(VectorPreferences.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY)
if (supportNotificationChannels()) { if (supportNotificationChannels()) {
@ -92,13 +83,13 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseF
if (supportNotificationChannels()) { if (supportNotificationChannels()) {
ringtonePreference.isVisible = false ringtonePreference.isVisible = false
} else { } else {
ringtonePreference.summary = vectorPreferences.getNotificationRingToneName() ringtonePreference.summary = VectorPreferences.getNotificationRingToneName(requireContext())
ringtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { ringtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER) val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION) intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
if (null != vectorPreferences.getNotificationRingTone()) { if (null != VectorPreferences.getNotificationRingTone(requireContext())) {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, vectorPreferences.getNotificationRingTone()) intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, VectorPreferences.getNotificationRingTone(requireContext()))
} }
startActivityForResult(intent, REQUEST_NOTIFICATION_RINGTONE) startActivityForResult(intent, REQUEST_NOTIFICATION_RINGTONE)
@ -161,12 +152,13 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseF
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
when (requestCode) { when (requestCode) {
REQUEST_NOTIFICATION_RINGTONE -> { REQUEST_NOTIFICATION_RINGTONE -> {
vectorPreferences.setNotificationRingTone(data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?) VectorPreferences.setNotificationRingTone(requireContext(),
data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
// test if the selected ring tone can be played // test if the selected ring tone can be played
val notificationRingToneName = vectorPreferences.getNotificationRingToneName() val notificationRingToneName = VectorPreferences.getNotificationRingToneName(requireContext())
if (null != notificationRingToneName) { if (null != notificationRingToneName) {
vectorPreferences.setNotificationRingTone(vectorPreferences.getNotificationRingTone()) VectorPreferences.setNotificationRingTone(requireContext(), VectorPreferences.getNotificationRingTone(requireContext()))
findPreference(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY).summary = notificationRingToneName findPreference(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY).summary = notificationRingToneName
} }
} }

View File

@ -23,23 +23,15 @@ import androidx.preference.Preference
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.utils.copyToClipboard import im.vector.riotx.core.utils.copyToClipboard
import im.vector.riotx.core.utils.displayInWebView import im.vector.riotx.core.utils.displayInWebView
import im.vector.riotx.features.version.VersionProvider import im.vector.riotx.features.version.getVersion
import javax.inject.Inject
class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() { class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() {
override var titleRes = R.string.preference_root_help_about override var titleRes = R.string.preference_root_help_about
override val preferenceXmlRes = R.xml.vector_settings_help_about override val preferenceXmlRes = R.xml.vector_settings_help_about
@Inject lateinit var versionProvider: VersionProvider
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun bindPref() { override fun bindPref() {
// preference to start the App info screen, to facilitate App permissions access // preference to start the App info screen, to facilitate App permissions access
findPreference(APP_INFO_LINK_PREFERENCE_KEY) findPreference(APP_INFO_LINK_PREFERENCE_KEY)
@ -62,7 +54,7 @@ class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() {
// application version // application version
(findPreference(VectorPreferences.SETTINGS_VERSION_PREFERENCE_KEY)).let { (findPreference(VectorPreferences.SETTINGS_VERSION_PREFERENCE_KEY)).let {
it.summary = versionProvider.getVersion(longFormat = false, useBuildNumber = true) it.summary = getVersion(longFormat = false, useBuildNumber = true)
it.setOnPreferenceClickListener { pref -> it.setOnPreferenceClickListener { pref ->
copyToClipboard(requireContext(), pref.summary) copyToClipboard(requireContext(), pref.summary)

View File

@ -36,7 +36,6 @@ class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment(
@Inject lateinit var pushManager: PushersManager @Inject lateinit var pushManager: PushersManager
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var vectorPreferences: VectorPreferences
override fun bindPref() { override fun bindPref() {
findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY).let { pref -> findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY).let { pref ->
@ -85,7 +84,7 @@ class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment(
val switchPref = preference as SwitchPreference val switchPref = preference as SwitchPreference
if (switchPref.isChecked) { if (switchPref.isChecked) {
FcmHelper.getFcmToken(requireContext())?.let { FcmHelper.getFcmToken(requireContext())?.let {
if (vectorPreferences.areNotificationEnabledForDevice()) { if (VectorPreferences.areNotificationEnabledForDevice(requireContext())) {
pushManager.registerPusherWithFcmKey(it) pushManager.registerPusherWithFcmKey(it)
} }
} }

View File

@ -44,7 +44,6 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
} }
@Inject lateinit var vectorConfiguration: VectorConfiguration @Inject lateinit var vectorConfiguration: VectorConfiguration
@Inject lateinit var vectorPreferences: VectorPreferences
override fun injectWith(injector: ScreenComponent) { override fun injectWith(injector: ScreenComponent) {
injector.inject(this) injector.inject(this)
@ -114,17 +113,17 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
// update keep medias period // update keep medias period
findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY).let { findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY).let {
it.summary = vectorPreferences.getSelectedMediasSavingPeriodString() it.summary = VectorPreferences.getSelectedMediasSavingPeriodString(requireContext())
it.onPreferenceClickListener = Preference.OnPreferenceClickListener { it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
context?.let { context: Context -> context?.let { context: Context ->
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setSingleChoiceItems(R.array.media_saving_choice, .setSingleChoiceItems(R.array.media_saving_choice,
vectorPreferences.getSelectedMediasSavingPeriod()) { d, n -> VectorPreferences.getSelectedMediasSavingPeriod(context)) { d, n ->
vectorPreferences.setSelectedMediasSavingPeriod(n) VectorPreferences.setSelectedMediasSavingPeriod(context, n)
d.cancel() d.cancel()
it.summary = vectorPreferences.getSelectedMediasSavingPeriodString() it.summary = VectorPreferences.getSelectedMediasSavingPeriodString(context)
} }
.show() .show()
} }

View File

@ -42,7 +42,6 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.dialogs.ExportKeysDialog import im.vector.riotx.core.dialogs.ExportKeysDialog
import im.vector.riotx.core.intent.ExternalIntentData import im.vector.riotx.core.intent.ExternalIntentData
import im.vector.riotx.core.intent.analyseIntent import im.vector.riotx.core.intent.analyseIntent
@ -58,7 +57,6 @@ import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() { class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
@ -129,12 +127,6 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
findPreference(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY) as SwitchPreference findPreference(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY) as SwitchPreference
} }
@Inject lateinit var vectorPreferences: VectorPreferences
override fun injectWith(injector: ScreenComponent) {
injector.inject(this)
}
override fun bindPref() { override fun bindPref() {
// Push target // Push target
refreshPushersList() refreshPushersList()
@ -150,20 +142,20 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
// Analytics tracking management // Analytics tracking management
(findPreference(VectorPreferences.SETTINGS_USE_ANALYTICS_KEY) as SwitchPreference).let { (findPreference(VectorPreferences.SETTINGS_USE_ANALYTICS_KEY) as SwitchPreference).let {
// On if the analytics tracking is activated // On if the analytics tracking is activated
it.isChecked = vectorPreferences.useAnalytics() it.isChecked = VectorPreferences.useAnalytics(requireContext())
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
vectorPreferences.setUseAnalytics(newValue as Boolean) VectorPreferences.setUseAnalytics(requireContext(), newValue as Boolean)
true true
} }
} }
// Rageshake Management // Rageshake Management
(findPreference(VectorPreferences.SETTINGS_USE_RAGE_SHAKE_KEY) as SwitchPreference).let { (findPreference(VectorPreferences.SETTINGS_USE_RAGE_SHAKE_KEY) as SwitchPreference).let {
it.isChecked = vectorPreferences.useRageshake() it.isChecked = VectorPreferences.useRageshake(requireContext())
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
vectorPreferences.setUseRageshake(newValue as Boolean) VectorPreferences.setUseRageshake(requireContext(), newValue as Boolean)
true true
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package im.vector.riotx.features.settings.troubleshoot package im.vector.riotx.features.settings.troubleshoot
import androidx.appcompat.app.AppCompatActivity
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
@ -23,20 +24,20 @@ import javax.inject.Inject
/** /**
* Checks if notifications are enable in the system settings for this app. * Checks if notifications are enable in the system settings for this app.
*/ */
class TestDeviceSettings @Inject constructor(private val vectorPreferences: VectorPreferences, class TestDeviceSettings @Inject constructor(private val context: AppCompatActivity,
private val stringProvider: StringProvider) private val stringProvider: StringProvider)
: TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) { : TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) {
override fun perform() { override fun perform() {
if (vectorPreferences.areNotificationEnabledForDevice()) { if (VectorPreferences.areNotificationEnabledForDevice(context)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_success) description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_success)
quickFix = null quickFix = null
status = TestStatus.SUCCESS status = TestStatus.SUCCESS
} else { } else {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) {
override fun doFix() { override fun doFix() {
vectorPreferences.setNotificationEnabledForDevice(true) VectorPreferences.setNotificationEnabledForDevice(context, true)
manager?.retry() manager?.retry()
} }
} }

View File

@ -0,0 +1,49 @@
/*
* 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.version
import im.vector.riotx.BuildConfig
fun getVersion(longFormat: Boolean, useBuildNumber: Boolean): String {
var result = "${BuildConfig.VERSION_NAME} [${BuildConfig.VERSION_CODE}]"
var flavor = BuildConfig.SHORT_FLAVOR_DESCRIPTION
if (flavor.isNotBlank()) {
flavor += "-"
}
var gitVersion = BuildConfig.GIT_REVISION
val gitRevisionDate = BuildConfig.GIT_REVISION_DATE
val buildNumber = BuildConfig.BUILD_NUMBER
var useLongFormat = longFormat
if (useBuildNumber && buildNumber != "0") {
// It's a build from CI
gitVersion = "b$buildNumber"
useLongFormat = false
}
result += if (useLongFormat) {
" ($flavor$gitVersion-$gitRevisionDate)"
} else {
" ($flavor$gitVersion)"
}
return result
}

View File

@ -1,54 +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.version
import im.vector.riotx.BuildConfig
import im.vector.riotx.core.resources.VersionCodeProvider
import javax.inject.Inject
class VersionProvider @Inject constructor(private val versionCodeProvider: VersionCodeProvider) {
fun getVersion(longFormat: Boolean, useBuildNumber: Boolean): String {
var result = "${BuildConfig.VERSION_NAME} [${versionCodeProvider.getVersionCode()}]"
var flavor = BuildConfig.SHORT_FLAVOR_DESCRIPTION
if (flavor.isNotBlank()) {
flavor += "-"
}
var gitVersion = BuildConfig.GIT_REVISION
val gitRevisionDate = BuildConfig.GIT_REVISION_DATE
val buildNumber = BuildConfig.BUILD_NUMBER
var useLongFormat = longFormat
if (useBuildNumber && buildNumber != "0") {
// It's a build from CI
gitVersion = "b$buildNumber"
useLongFormat = false
}
result += if (useLongFormat) {
" ($flavor$gitVersion-$gitRevisionDate)"
} else {
" ($flavor$gitVersion)"
}
return result
}
}

View File

@ -127,7 +127,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:fastScrollEnabled="true" android:fastScrollEnabled="true"
android:overScrollMode="always"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -95,7 +95,6 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:fastScrollEnabled="true" android:fastScrollEnabled="true"
android:overScrollMode="always"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@ -9,7 +9,6 @@
android:id="@+id/groupListEpoxyRecyclerView" android:id="@+id/groupListEpoxyRecyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:overScrollMode="always"
tools:listitem="@layout/item_group" /> tools:listitem="@layout/item_group" />
</im.vector.riotx.core.platform.StateView> </im.vector.riotx.core.platform.StateView>

View File

@ -12,7 +12,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?riotx_header_panel_background" android:background="?riotx_header_panel_background"
android:overScrollMode="always"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_public_room" /> tools:listitem="@layout/item_public_room" />

View File

@ -11,8 +11,8 @@
style="@style/VectorToolbarStyle" style="@style/VectorToolbarStyle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="?actionBarSize" android:layout_height="?actionBarSize"
android:elevation="4dp"
android:transitionName="toolbar" android:transitionName="toolbar"
android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -71,38 +71,14 @@
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
<!-- Trick to remove surrounding padding (clip frome wrapping frame) -->
<FrameLayout
android:id="@+id/syncProgressBarWrap"
android:layout_width="match_parent"
android:layout_height="3dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/roomToolbar"
tools:visibility="visible">
<ProgressBar
android:id="@+id/syncProgressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="14dp"
android:layout_gravity="center"
android:background="?riotx_header_panel_background"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
<com.airbnb.epoxy.EpoxyRecyclerView <com.airbnb.epoxy.EpoxyRecyclerView
android:id="@+id/recyclerView" android:id="@+id/recyclerView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:overScrollMode="always"
app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier" app:layout_constraintBottom_toTopOf="@+id/recyclerViewBarrier"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/syncProgressBarWrap" app:layout_constraintTop_toBottomOf="@id/roomToolbar"
tools:listitem="@layout/item_timeline_event_base" /> tools:listitem="@layout/item_timeline_event_base" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
@ -122,7 +98,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<im.vector.riotx.core.ui.views.NotificationAreaView <im.vector.riotx.core.platform.NotificationAreaView
android:id="@+id/notificationAreaView" android:id="@+id/notificationAreaView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

Some files were not shown because too many files have changed in this diff Show More