forked from GitHub-Mirror/riotX-android
Compare commits
51 Commits
feature/do
...
feature/fi
Author | SHA1 | Date | |
---|---|---|---|
b5af62c3ea | |||
a51d96bf00 | |||
7e142d201d | |||
2be6058971 | |||
49d73f360e | |||
bd88d85a21 | |||
be4fc5cce6 | |||
704da1be55 | |||
5d002532d3 | |||
d4161e9a1a | |||
7966ebef03 | |||
ed5faca5d2 | |||
8ca829d538 | |||
e7819ce678 | |||
5402902bc2 | |||
bc1350aaf5 | |||
fd74e3dfb1 | |||
e0628da1cb | |||
aa4e74e986 | |||
6cc0c0672e | |||
501474b720 | |||
e11c66035c | |||
3d2d219d79 | |||
63af03bedd | |||
d3827b8673 | |||
4ca2531e47 | |||
4e8dc72439 | |||
25a4240a5a | |||
b9cfda23b6 | |||
06dcf75a32 | |||
21deb2551d | |||
70639f180c | |||
1dbb02a80d | |||
825463d9cd | |||
c313ce78cb | |||
39f58d048b | |||
3f792c7a84 | |||
347dcb469a | |||
79fb1985aa | |||
e216cd15a8 | |||
37fde374b3 | |||
f7b471f141 | |||
93fd56a7ca | |||
5a9d88e791 | |||
eaf6a9923a | |||
d98567045c | |||
b4ce8748cb | |||
9d5433a857 | |||
6d4ee83e65 | |||
6e44cca17d | |||
0a73887c70 |
33
CHANGES.md
33
CHANGES.md
@ -1,8 +1,33 @@
|
||||
Changes in RiotX 0.3.0 (2019-XX-XX)
|
||||
Changes in RiotX 0.4.0 (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Display read receipts in timeline (#81)
|
||||
|
||||
Improvements:
|
||||
- Reactions: Reinstate the ability to react with non-unicode keys (#307)
|
||||
|
||||
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)
|
||||
- Some video won't play
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
|
||||
Changes in RiotX 0.3.0 (2019-08-08)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Create Direct Room flow
|
||||
- Handle `/markdown` command
|
||||
|
||||
Improvements:
|
||||
- UI for pending edits (#193)
|
||||
@ -11,9 +36,10 @@ Improvements:
|
||||
- Enable proper cancellation of suspending functions (including db transaction)
|
||||
- Enhances network connectivity checks in SDK
|
||||
- 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:
|
||||
-
|
||||
- Show sync progress also in room detail screen (#403)
|
||||
|
||||
Bugfix:
|
||||
- Edited message: link confusion when (edited) appears in body (#398)
|
||||
@ -23,9 +49,6 @@ Bugfix:
|
||||
- Fix clear cache (#408) and Logout (#205)
|
||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||
|
||||
|
@ -18,6 +18,7 @@ package im.vector.matrix.rx
|
||||
|
||||
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.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import io.reactivex.Observable
|
||||
@ -49,6 +50,10 @@ class RxRoom(private val room: Room) {
|
||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||
}
|
||||
|
||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Room.rx(): RxRoom {
|
||||
|
@ -16,8 +16,9 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
|
||||
data class ReadReceipt(
|
||||
val userId: String,
|
||||
val eventId: String,
|
||||
val user: User,
|
||||
val originServerTs: Long
|
||||
)
|
@ -16,7 +16,9 @@
|
||||
|
||||
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.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.
|
||||
@ -39,4 +41,6 @@ interface ReadService {
|
||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||
|
||||
fun isEventRead(eventId: String): Boolean
|
||||
|
||||
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||
}
|
@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.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.isReply
|
||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||
@ -37,7 +38,8 @@ data class TimelineEvent(
|
||||
val senderName: String?,
|
||||
val isUniqueDisplayName: Boolean,
|
||||
val senderAvatar: String?,
|
||||
val annotations: EventAnnotationsSummary? = null
|
||||
val annotations: EventAnnotationsSummary? = null,
|
||||
val readReceipts: List<ReadReceipt> = emptyList()
|
||||
) {
|
||||
|
||||
val metadata = HashMap<String, Any>()
|
||||
@ -65,8 +67,8 @@ data class TimelineEvent(
|
||||
"$name (${root.senderId})"
|
||||
}
|
||||
}
|
||||
?: root.senderId
|
||||
?: ""
|
||||
?: root.senderId
|
||||
?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +96,7 @@ fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
|
||||
* Get last MessageContent, after a possible edition
|
||||
*/
|
||||
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
|
||||
?: root.getClearContent().toModel()
|
||||
?: root.getClearContent().toModel()
|
||||
|
||||
|
||||
fun TimelineEvent.getTextEditableContent(): String? {
|
||||
|
@ -25,12 +25,12 @@ interface TimelineService {
|
||||
|
||||
/**
|
||||
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
||||
* You can filter the type you want to grab with the allowedTypes param.
|
||||
* You can also configure some settings with the [settings] param.
|
||||
* @param eventId the optional initial eventId.
|
||||
* @param allowedTypes the optional filter types
|
||||
* @param settings settings to configure the timeline.
|
||||
* @return the instantiated timeline
|
||||
*/
|
||||
fun createTimeline(eventId: String?, allowedTypes: List<String>? = null): Timeline
|
||||
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
|
||||
|
||||
|
||||
fun getTimeLineEvent(eventId: String): TimelineEvent?
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
)
|
@ -23,9 +23,12 @@ 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.model.ChunkEntity
|
||||
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.TimelineEventEntityFields
|
||||
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.extensions.assertIsManaged
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
@ -133,6 +136,28 @@ internal fun ChunkEntity.add(roomId: String,
|
||||
}
|
||||
|
||||
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 {
|
||||
it.root = event.toEntity(roomId).apply {
|
||||
this.stateIndex = currentStateIndex
|
||||
@ -140,9 +165,10 @@ internal fun ChunkEntity.add(roomId: String,
|
||||
this.displayIndex = currentDisplayIndex
|
||||
this.sendState = SendState.SYNCED
|
||||
}
|
||||
it.eventId = event.eventId ?: ""
|
||||
it.eventId = eventId
|
||||
it.roomId = roomId
|
||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, it.eventId).findFirst()
|
||||
it.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
it.readReceipts = readReceiptsSummaryEntity
|
||||
}
|
||||
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||
timelineEvents.add(position, eventEntity)
|
||||
@ -150,14 +176,14 @@ internal fun ChunkEntity.add(roomId: String,
|
||||
|
||||
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||
return when (direction) {
|
||||
PaginationDirection.FORWARDS -> forwardsDisplayIndex
|
||||
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
|
||||
} ?: defaultValue
|
||||
PaginationDirection.FORWARDS -> forwardsDisplayIndex
|
||||
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
|
||||
} ?: defaultValue
|
||||
}
|
||||
|
||||
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
|
||||
return when (direction) {
|
||||
PaginationDirection.FORWARDS -> forwardsStateIndex
|
||||
PaginationDirection.BACKWARDS -> backwardsStateIndex
|
||||
} ?: defaultValue
|
||||
PaginationDirection.FORWARDS -> forwardsStateIndex
|
||||
PaginationDirection.BACKWARDS -> backwardsStateIndex
|
||||
} ?: defaultValue
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,8 @@ import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryMapper @Inject constructor(
|
||||
val cryptoService: CryptoService
|
||||
val cryptoService: CryptoService,
|
||||
val timelineEventMapper: TimelineEventMapper
|
||||
) {
|
||||
|
||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||
@ -34,7 +35,9 @@ internal class RoomSummaryMapper @Inject constructor(
|
||||
RoomTag(it.tagName, it.tagOrder)
|
||||
}
|
||||
|
||||
val latestEvent = roomSummaryEntity.latestEvent?.asDomain()
|
||||
val latestEvent = roomSummaryEntity.latestEvent?.let {
|
||||
timelineEventMapper.map(it)
|
||||
}
|
||||
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
|
||||
//TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||
//for now decrypt sync
|
||||
|
@ -17,29 +17,38 @@
|
||||
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.room.model.ReadReceipt
|
||||
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object TimelineEventMapper {
|
||||
|
||||
fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
|
||||
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
||||
|
||||
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(
|
||||
root = timelineEventEntity.root?.asDomain()
|
||||
?: Event("", timelineEventEntity.eventId),
|
||||
?: Event("", timelineEventEntity.eventId),
|
||||
annotations = timelineEventEntity.annotations?.asDomain(),
|
||||
localId = timelineEventEntity.localId,
|
||||
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
|
||||
senderName = timelineEventEntity.senderName,
|
||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||
senderAvatar = timelineEventEntity.senderAvatar
|
||||
senderAvatar = timelineEventEntity.senderAvatar,
|
||||
readReceipts = readReceipts?.sortedByDescending {
|
||||
it.originServerTs
|
||||
} ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun TimelineEventEntity.asDomain(): TimelineEvent {
|
||||
return TimelineEventMapper.map(this)
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,13 +17,18 @@
|
||||
package im.vector.matrix.android.internal.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.RealmResults
|
||||
import io.realm.annotations.LinkingObjects
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class ReadReceiptEntity(@PrimaryKey var primaryKey: String = "",
|
||||
var userId: String = "",
|
||||
var eventId: String = "",
|
||||
var roomId: String = "",
|
||||
var originServerTs: Double = 0.0
|
||||
var eventId: String = "",
|
||||
var roomId: String = "",
|
||||
var userId: String = "",
|
||||
var originServerTs: Double = 0.0
|
||||
) : RealmObject() {
|
||||
companion object
|
||||
|
||||
@LinkingObjects("readReceipts")
|
||||
val summary: RealmResults<ReadReceiptsSummaryEntity>? = null
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
}
|
@ -22,26 +22,27 @@ import io.realm.annotations.RealmModule
|
||||
* Realm module for Session
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
classes = [
|
||||
ChunkEntity::class,
|
||||
EventEntity::class,
|
||||
TimelineEventEntity::class,
|
||||
FilterEntity::class,
|
||||
GroupEntity::class,
|
||||
GroupSummaryEntity::class,
|
||||
ReadReceiptEntity::class,
|
||||
RoomEntity::class,
|
||||
RoomSummaryEntity::class,
|
||||
RoomTagEntity::class,
|
||||
SyncEntity::class,
|
||||
UserEntity::class,
|
||||
EventAnnotationsSummaryEntity::class,
|
||||
ReactionAggregatedSummaryEntity::class,
|
||||
EditAggregatedSummaryEntity::class,
|
||||
PushRulesEntity::class,
|
||||
PushRuleEntity::class,
|
||||
PushConditionEntity::class,
|
||||
PusherEntity::class,
|
||||
PusherDataEntity::class
|
||||
])
|
||||
classes = [
|
||||
ChunkEntity::class,
|
||||
EventEntity::class,
|
||||
TimelineEventEntity::class,
|
||||
FilterEntity::class,
|
||||
GroupEntity::class,
|
||||
GroupSummaryEntity::class,
|
||||
ReadReceiptEntity::class,
|
||||
RoomEntity::class,
|
||||
RoomSummaryEntity::class,
|
||||
RoomTagEntity::class,
|
||||
SyncEntity::class,
|
||||
UserEntity::class,
|
||||
EventAnnotationsSummaryEntity::class,
|
||||
ReactionAggregatedSummaryEntity::class,
|
||||
EditAggregatedSummaryEntity::class,
|
||||
PushRulesEntity::class,
|
||||
PushRuleEntity::class,
|
||||
PushConditionEntity::class,
|
||||
PusherEntity::class,
|
||||
PusherDataEntity::class,
|
||||
ReadReceiptsSummaryEntity::class
|
||||
])
|
||||
internal class SessionRealmModule
|
||||
|
@ -30,7 +30,8 @@ internal open class TimelineEventEntity(var localId: Long = 0,
|
||||
var senderName: String? = null,
|
||||
var isUniqueDisplayName: Boolean = false,
|
||||
var senderAvatar: String? = null,
|
||||
var senderMembershipEvent: EventEntity? = null
|
||||
var senderMembershipEvent: EventEntity? = null,
|
||||
var readReceipts: ReadReceiptsSummaryEntity? = null
|
||||
) : RealmObject() {
|
||||
|
||||
@LinkingObjects("timelineEvents")
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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"*}"""
|
||||
|
||||
}
|
@ -26,4 +26,22 @@ internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, use
|
||||
return realm.where<ReadReceiptEntity>()
|
||||
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
|
||||
.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
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.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
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.di
|
||||
|
||||
import com.squareup.inject.assisted.dagger2.AssistedModule
|
||||
import dagger.Module
|
||||
|
||||
@AssistedModule
|
||||
@Module(includes = [AssistedInject_SessionAssistedInjectModule::class])
|
||||
interface SessionAssistedInjectModule
|
@ -22,6 +22,7 @@ import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.internal.crypto.CryptoModule
|
||||
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.session.cache.CacheModule
|
||||
import im.vector.matrix.android.internal.session.content.ContentModule
|
||||
@ -59,7 +60,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
CacheModule::class,
|
||||
CryptoModule::class,
|
||||
PushersModule::class,
|
||||
AccountDataModule::class
|
||||
AccountDataModule::class,
|
||||
SessionAssistedInjectModule::class
|
||||
]
|
||||
)
|
||||
@SessionScope
|
||||
|
@ -36,7 +36,9 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
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.RoomFactory
|
||||
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.tombstone.RoomTombstoneEventLiveObserver
|
||||
@ -114,7 +116,6 @@ internal abstract class SessionModule {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Binds
|
||||
abstract fun bindSession(session: DefaultSession): Session
|
||||
|
||||
|
@ -16,70 +16,46 @@
|
||||
|
||||
package im.vector.matrix.android.internal.session.room
|
||||
|
||||
import android.content.Context
|
||||
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.room.Room
|
||||
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.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.SetReadMarkersTask
|
||||
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.LocalEchoEventFactory
|
||||
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.GetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
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) {
|
||||
|
||||
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 interface RoomFactory {
|
||||
fun create(roomId: String): Room
|
||||
}
|
||||
|
||||
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(
|
||||
roomId,
|
||||
monarchy,
|
||||
roomSummaryMapper,
|
||||
timelineService,
|
||||
sendService,
|
||||
stateService,
|
||||
readService,
|
||||
timelineServiceFactory.create(roomId),
|
||||
sendServiceFactory.create(roomId),
|
||||
stateServiceFactory.create(roomId),
|
||||
readServiceFactory.create(roomId),
|
||||
cryptoService,
|
||||
relationService,
|
||||
roomMembersService
|
||||
relationServiceFactory.create(roomId),
|
||||
membershipServiceFactory.create(roomId)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -22,12 +22,6 @@ import dagger.Provides
|
||||
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.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.SessionScope
|
||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||
@ -37,7 +31,6 @@ 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.GetThirdPartyProtocolsTask
|
||||
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.joining.DefaultInviteTask
|
||||
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask
|
||||
@ -47,15 +40,20 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.Default
|
||||
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
||||
import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask
|
||||
import im.vector.matrix.android.internal.session.room.prune.PruneEventTask
|
||||
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
|
||||
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.relation.*
|
||||
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
||||
import im.vector.matrix.android.internal.session.room.relation.DefaultFetchEditHistoryTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.DefaultFindReactionEventForUndoTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.DefaultUpdateQuickReactionTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.FetchEditHistoryTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask
|
||||
import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask
|
||||
import im.vector.matrix.android.internal.session.room.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.timeline.*
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||
import retrofit2.Retrofit
|
||||
|
||||
@Module
|
||||
@ -71,6 +69,9 @@ internal abstract class RoomModule {
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomFactory(roomFactory: DefaultRoomFactory): RoomFactory
|
||||
|
||||
@Binds
|
||||
abstract fun bindRoomService(roomService: DefaultRoomService): RoomService
|
||||
|
||||
@ -98,24 +99,15 @@ internal abstract class RoomModule {
|
||||
@Binds
|
||||
abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindMembershipService(membershipService: DefaultMembershipService): MembershipService
|
||||
|
||||
@Binds
|
||||
abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindReadService(readService: DefaultReadService): ReadService
|
||||
|
||||
@Binds
|
||||
abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindRelationService(relationService: DefaultRelationService): RelationService
|
||||
|
||||
@Binds
|
||||
abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
|
||||
|
||||
@ -125,21 +117,12 @@ internal abstract class RoomModule {
|
||||
@Binds
|
||||
abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSendService(sendService: DefaultSendService): SendService
|
||||
|
||||
@Binds
|
||||
abstract fun bindStateService(stateService: DefaultStateService): StateService
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
||||
|
||||
@Binds
|
||||
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
package im.vector.matrix.android.internal.session.room.membership
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
@ -31,17 +33,21 @@ 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.configureWith
|
||||
import im.vector.matrix.android.internal.util.fetchCopied
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultMembershipService @Inject constructor(private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val inviteTask: InviteTask,
|
||||
private val joinTask: JoinRoomTask,
|
||||
private val leaveRoomTask: LeaveRoomTask
|
||||
internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val inviteTask: InviteTask,
|
||||
private val joinTask: JoinRoomTask,
|
||||
private val leaveRoomTask: LeaveRoomTask
|
||||
) : MembershipService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): MembershipService
|
||||
}
|
||||
|
||||
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
|
||||
return loadRoomMembersTask
|
||||
|
@ -16,24 +16,38 @@
|
||||
|
||||
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 im.vector.matrix.android.api.MatrixCallback
|
||||
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.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.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.findLastLiveChunkFromRoom
|
||||
import im.vector.matrix.android.internal.database.query.where
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultReadService @Inject constructor(private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val setReadMarkersTask: SetReadMarkersTask,
|
||||
private val credentials: Credentials) : ReadService {
|
||||
internal class DefaultReadService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val setReadMarkersTask: SetReadMarkersTask,
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||
private val credentials: Credentials
|
||||
) : ReadService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): ReadService
|
||||
}
|
||||
|
||||
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
||||
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
|
||||
@ -67,16 +81,28 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
||||
var isEventRead = false
|
||||
monarchy.doWithRealm {
|
||||
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
|
||||
?: return@doWithRealm
|
||||
?: return@doWithRealm
|
||||
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
|
||||
?: return@doWithRealm
|
||||
?: return@doWithRealm
|
||||
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
|
||||
?: Int.MIN_VALUE
|
||||
?: Int.MIN_VALUE
|
||||
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
|
||||
?: Int.MAX_VALUE
|
||||
?: Int.MAX_VALUE
|
||||
isEventRead = eventToCheckIndex <= readReceiptIndex
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
@ -45,19 +47,23 @@ import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.CancelableWork
|
||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultRelationService @Inject constructor(private val context: Context,
|
||||
private val credentials: Credentials,
|
||||
private val roomId: String,
|
||||
private val eventFactory: LocalEchoEventFactory,
|
||||
private val cryptoService: CryptoService,
|
||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor)
|
||||
internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val context: Context,
|
||||
private val credentials: Credentials,
|
||||
private val eventFactory: LocalEchoEventFactory,
|
||||
private val cryptoService: CryptoService,
|
||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor)
|
||||
: RelationService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): RelationService
|
||||
}
|
||||
|
||||
override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
|
||||
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
||||
.also {
|
||||
@ -148,9 +154,9 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||
compatibilityBodyText: String): Cancelable {
|
||||
val event = eventFactory
|
||||
.createReplaceTextOfReply(roomId,
|
||||
replyToEdit,
|
||||
originalEvent,
|
||||
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
|
||||
replyToEdit,
|
||||
originalEvent,
|
||||
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
|
||||
.also {
|
||||
saveLocalEcho(it)
|
||||
}
|
||||
@ -214,7 +220,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||
}
|
||||
return Transformations.map(liveEntity) { realmResults ->
|
||||
realmResults.firstOrNull()?.asDomain()
|
||||
?: EventAnnotationsSummary(eventId, emptyList(), null)
|
||||
?: EventAnnotationsSummary(eventId, emptyList(), null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,7 +233,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
||||
private fun saveLocalEcho(event: Event) {
|
||||
monarchy.writeAsync { realm ->
|
||||
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
|
||||
?: return@writeAsync
|
||||
?: return@writeAsync
|
||||
roomEntity.addSendingEvent(event)
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,22 @@
|
||||
package im.vector.matrix.android.internal.session.room.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import androidx.work.BackoffPolicy
|
||||
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 im.vector.matrix.android.api.auth.data.Credentials
|
||||
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.events.model.*
|
||||
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.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.MessageType
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
@ -47,18 +57,22 @@ import im.vector.matrix.android.internal.worker.startChain
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val UPLOAD_WORK = "UPLOAD_WORK"
|
||||
private const val BACKOFF_DELAY = 10_000L
|
||||
|
||||
internal class DefaultSendService @Inject constructor(private val context: Context,
|
||||
private val credentials: Credentials,
|
||||
private val roomId: String,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val cryptoService: CryptoService,
|
||||
private val monarchy: Monarchy)
|
||||
: SendService {
|
||||
internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val context: Context,
|
||||
private val credentials: Credentials,
|
||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||
private val cryptoService: CryptoService,
|
||||
private val monarchy: Monarchy
|
||||
) : SendService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): SendService
|
||||
}
|
||||
|
||||
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
||||
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||
@ -152,11 +166,11 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
||||
override fun deleteFailedEcho(localEcho: TimelineEvent) {
|
||||
monarchy.writeAsync { realm ->
|
||||
TimelineEventEntity.where(realm, eventId = localEcho.root.eventId
|
||||
?: "").findFirst()?.let {
|
||||
?: "").findFirst()?.let {
|
||||
it.deleteFromRealm()
|
||||
}
|
||||
EventEntity.where(realm, eventId = localEcho.root.eventId
|
||||
?: "").findFirst()?.let {
|
||||
?: "").findFirst()?.let {
|
||||
it.deleteFromRealm()
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
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.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
@ -29,13 +31,18 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultStateService @Inject constructor(private val roomId: String,
|
||||
@SessionDatabase
|
||||
private val realmConfiguration: RealmConfiguration,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendStateTask: SendStateTask) : StateService {
|
||||
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
@SessionDatabase
|
||||
private val realmConfiguration: RealmConfiguration,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val sendStateTask: SendStateTask
|
||||
) : StateService {
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): StateService
|
||||
}
|
||||
|
||||
override fun getStateEvent(eventType: String): Event? {
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
@ -45,10 +52,10 @@ internal class DefaultStateService @Inject constructor(private val roomId: Strin
|
||||
|
||||
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
|
||||
val params = SendStateTask.Params(roomId,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
mapOf(
|
||||
"topic" to topic
|
||||
))
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
mapOf(
|
||||
"topic" to topic
|
||||
))
|
||||
|
||||
|
||||
sendStateTask
|
||||
|
@ -19,21 +19,41 @@ package im.vector.matrix.android.internal.session.room.timeline
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
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.room.model.ReadReceipt
|
||||
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.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||
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.model.*
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
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.query.*
|
||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||
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.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.Debouncer
|
||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||
import im.vector.matrix.android.internal.util.createUIHandler
|
||||
import io.realm.*
|
||||
import io.realm.OrderedCollectionChangeSet
|
||||
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 java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@ -42,7 +62,6 @@ import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
|
||||
private const val INITIAL_LOAD_SIZE = 30
|
||||
private const val MIN_FETCHING_COUNT = 30
|
||||
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
|
||||
|
||||
@ -53,9 +72,11 @@ internal class DefaultTimeline(
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val contextOfEventTask: GetContextOfEventTask,
|
||||
private val paginationTask: PaginationTask,
|
||||
cryptoService: CryptoService,
|
||||
private val allowedTypes: List<String>?
|
||||
) : Timeline {
|
||||
private val cryptoService: CryptoService,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val settings: TimelineSettings,
|
||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts
|
||||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||
|
||||
private companion object {
|
||||
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
||||
@ -77,6 +98,8 @@ internal class DefaultTimeline(
|
||||
private val debouncer = Debouncer(mainHandler)
|
||||
|
||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||
|
||||
private var roomEntity: RoomEntity? = null
|
||||
|
||||
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
||||
@ -87,11 +110,8 @@ internal class DefaultTimeline(
|
||||
private val backwardsPaginationState = AtomicReference(PaginationState())
|
||||
private val forwardsPaginationState = AtomicReference(PaginationState())
|
||||
|
||||
|
||||
private val timelineID = UUID.randomUUID().toString()
|
||||
|
||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||
|
||||
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
||||
|
||||
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
|
||||
@ -130,9 +150,9 @@ internal class DefaultTimeline(
|
||||
val eventEntity = results[index]
|
||||
eventEntity?.eventId?.let { eventId ->
|
||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||
//Update the relation of existing event
|
||||
//Update an existing event
|
||||
builtEvents[builtIndex]?.let { te ->
|
||||
builtEvents[builtIndex] = eventEntity.asDomain()
|
||||
builtEvents[builtIndex] = buildTimelineEvent(eventEntity)
|
||||
hasChanged = true
|
||||
}
|
||||
}
|
||||
@ -162,34 +182,8 @@ internal class DefaultTimeline(
|
||||
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) {
|
||||
BACKGROUND_HANDLER.post {
|
||||
@ -234,15 +228,20 @@ internal class DefaultTimeline(
|
||||
}
|
||||
|
||||
liveEvents = buildEventQuery(realm)
|
||||
.filterEventsWithSettings()
|
||||
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
.findAllAsync()
|
||||
.also { it.addChangeListener(eventsChangeListener) }
|
||||
|
||||
isReady.set(true)
|
||||
|
||||
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
||||
.findAllAsync()
|
||||
.also { it.addChangeListener(relationsListener) }
|
||||
|
||||
if (settings.buildReadReceipts) {
|
||||
hiddenReadReceipts.start(realm, liveEvents, this)
|
||||
}
|
||||
|
||||
isReady.set(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -256,6 +255,9 @@ internal class DefaultTimeline(
|
||||
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
||||
eventRelations.removeAllChangeListeners()
|
||||
liveEvents.removeAllChangeListeners()
|
||||
if (settings.buildReadReceipts) {
|
||||
hiddenReadReceipts.dispose()
|
||||
}
|
||||
backgroundRealm.getAndSet(null).also {
|
||||
it.close()
|
||||
}
|
||||
@ -267,12 +269,28 @@ internal class DefaultTimeline(
|
||||
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 fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
||||
return Realm.getInstance(realmConfiguration).use { localRealm ->
|
||||
val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction)
|
||||
?: return false
|
||||
?: return false
|
||||
if (direction == Timeline.Direction.FORWARDS) {
|
||||
if (findCurrentChunk(localRealm)?.isLastForward == true) {
|
||||
return false
|
||||
@ -329,9 +347,11 @@ internal class DefaultTimeline(
|
||||
val sendingEvents = ArrayList<TimelineEvent>()
|
||||
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
||||
roomEntity?.sendingTimelineEvents
|
||||
?.filter { allowedTypes?.contains(it.root?.type) ?: false }
|
||||
?.where()
|
||||
?.filterEventsWithSettings()
|
||||
?.findAll()
|
||||
?.forEach {
|
||||
sendingEvents.add(it.asDomain())
|
||||
sendingEvents.add(timelineEventMapper.map(it))
|
||||
}
|
||||
}
|
||||
return sendingEvents
|
||||
@ -378,7 +398,7 @@ internal class DefaultTimeline(
|
||||
if (initialEventId != null && shouldFetchInitialEvent) {
|
||||
fetchEvent(initialEventId)
|
||||
} else {
|
||||
val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size)
|
||||
val count = Math.min(settings.initialSize, liveEvents.size)
|
||||
if (isLive) {
|
||||
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
||||
} else {
|
||||
@ -395,9 +415,9 @@ internal class DefaultTimeline(
|
||||
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
|
||||
val token = getTokenLive(direction) ?: return
|
||||
val params = PaginationTask.Params(roomId = roomId,
|
||||
from = token,
|
||||
direction = direction.toPaginationDirection(),
|
||||
limit = limit)
|
||||
from = token,
|
||||
direction = direction.toPaginationDirection(),
|
||||
limit = limit)
|
||||
|
||||
Timber.v("Should fetch $limit items $direction")
|
||||
cancelableBag += paginationTask
|
||||
@ -463,10 +483,11 @@ internal class DefaultTimeline(
|
||||
nextDisplayIndex = offsetIndex + 1
|
||||
}
|
||||
offsetResults.forEach { eventEntity ->
|
||||
val timelineEvent = eventEntity.asDomain()
|
||||
|
||||
val timelineEvent = buildTimelineEvent(eventEntity)
|
||||
|
||||
if (timelineEvent.isEncrypted()
|
||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
|
||||
}
|
||||
|
||||
@ -481,6 +502,12 @@ internal class DefaultTimeline(
|
||||
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
|
||||
*/
|
||||
@ -498,7 +525,6 @@ internal class DefaultTimeline(
|
||||
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
||||
}
|
||||
return offsetQuery
|
||||
.filterAllowedTypes()
|
||||
.limit(count)
|
||||
.findAll()
|
||||
}
|
||||
@ -545,7 +571,7 @@ internal class DefaultTimeline(
|
||||
debouncer.debounce("post_snapshot", runnable, 50)
|
||||
}
|
||||
|
||||
// Extension methods ***************************************************************************
|
||||
// Extension methods ***************************************************************************
|
||||
|
||||
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||
@ -557,16 +583,20 @@ internal class DefaultTimeline(
|
||||
} else {
|
||||
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||
}
|
||||
.filterAllowedTypes()
|
||||
.filterEventsWithSettings()
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
private fun RealmQuery<TimelineEventEntity>.filterAllowedTypes(): RealmQuery<TimelineEventEntity> {
|
||||
if (allowedTypes != null) {
|
||||
`in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray())
|
||||
private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> {
|
||||
if (settings.filterTypes) {
|
||||
`in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray())
|
||||
}
|
||||
if (settings.filterEdits) {
|
||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private data class PaginationState(
|
||||
|
@ -18,28 +18,38 @@ package im.vector.matrix.android.internal.session.room.timeline
|
||||
|
||||
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 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.TimelineEvent
|
||||
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.mapper.asDomain
|
||||
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||
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.query.where
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val contextOfEventTask: GetContextOfEventTask,
|
||||
private val cryptoService: CryptoService,
|
||||
private val paginationTask: PaginationTask
|
||||
internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
private val monarchy: Monarchy,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val contextOfEventTask: GetContextOfEventTask,
|
||||
private val cryptoService: CryptoService,
|
||||
private val paginationTask: PaginationTask,
|
||||
private val timelineEventMapper: TimelineEventMapper,
|
||||
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||
) : TimelineService {
|
||||
|
||||
override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
fun create(roomId: String): TimelineService
|
||||
}
|
||||
|
||||
override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
|
||||
return DefaultTimeline(roomId,
|
||||
eventId,
|
||||
monarchy.realmConfiguration,
|
||||
@ -47,7 +57,10 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
||||
contextOfEventTask,
|
||||
paginationTask,
|
||||
cryptoService,
|
||||
allowedTypes)
|
||||
timelineEventMapper,
|
||||
settings,
|
||||
TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
|
||||
@ -55,7 +68,7 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
||||
.fetchCopyMap({
|
||||
TimelineEventEntity.where(it, eventId = eventId).findFirst()
|
||||
}, { entity, realm ->
|
||||
entity.asDomain()
|
||||
timelineEventMapper.map(entity)
|
||||
})
|
||||
}
|
||||
|
||||
@ -63,8 +76,8 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
||||
val liveData = RealmLiveData(monarchy.realmConfiguration) {
|
||||
TimelineEventEntity.where(it, eventId = eventId)
|
||||
}
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull()?.asDomain()
|
||||
return Transformations.map(liveData) { events ->
|
||||
events.firstOrNull()?.let { timelineEventMapper.map(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -17,6 +17,10 @@
|
||||
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.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 timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@ -29,34 +33,70 @@ import javax.inject.Inject
|
||||
// dict value ts value
|
||||
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() {
|
||||
|
||||
fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?) {
|
||||
fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) {
|
||||
if (content == null) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val readReceipts = mapContentToReadReceiptEntities(roomId, content)
|
||||
realm.insertOrUpdate(readReceipts)
|
||||
handleReadReceiptContent(realm, roomId, content, isInitialSync)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e("Fail to handle read receipt for room $roomId")
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapContentToReadReceiptEntities(roomId: String, content: ReadReceiptContent): List<ReadReceiptEntity> {
|
||||
return content
|
||||
.flatMap { (eventId, receiptDict) ->
|
||||
receiptDict
|
||||
.filterKeys { it == "m.read" }
|
||||
.flatMap { (_, userIdsDict) ->
|
||||
userIdsDict.map { (userId, paramsDict) ->
|
||||
val ts = paramsDict.filterKeys { it == "ts" }
|
||||
.values
|
||||
.firstOrNull() ?: 0.0
|
||||
val primaryKey = roomId + userId
|
||||
ReadReceiptEntity(primaryKey, userId, eventId, roomId, ts)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) {
|
||||
if (isInitialSync) {
|
||||
initialSyncStrategy(realm, roomId, content)
|
||||
} else {
|
||||
incrementalSyncStrategy(realm, roomId, content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun initialSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
|
||||
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>()
|
||||
for ((eventId, receiptDict) in content) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -62,11 +62,11 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
|
||||
}
|
||||
|
||||
fun handle(roomsSyncResponse: RoomsSyncResponse, reporter: DefaultInitialSyncProgressService? = null) {
|
||||
fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter)
|
||||
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter)
|
||||
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter)
|
||||
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter)
|
||||
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter)
|
||||
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter)
|
||||
}
|
||||
|
||||
//handle event for bing rule checks
|
||||
@ -89,12 +89,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, reporter: DefaultInitialSyncProgressService?) {
|
||||
private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService?) {
|
||||
|
||||
val rooms = when (handlingStrategy) {
|
||||
is HandlingStrategy.JOINED ->
|
||||
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) {
|
||||
handleJoinedRoom(realm, it.key, it.value)
|
||||
handleJoinedRoom(realm, it.key, it.value, isInitialSync)
|
||||
}
|
||||
is HandlingStrategy.INVITED ->
|
||||
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.4f) {
|
||||
@ -112,12 +112,21 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
|
||||
private fun handleJoinedRoom(realm: Realm,
|
||||
roomId: String,
|
||||
roomSync: RoomSync): RoomEntity {
|
||||
roomSync: RoomSync,
|
||||
isInitalSync: Boolean): RoomEntity {
|
||||
|
||||
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()
|
||||
?: realm.createObject(roomId)
|
||||
?: realm.createObject(roomId)
|
||||
|
||||
if (roomEntity.membership == Membership.INVITE) {
|
||||
roomEntity.chunks.deleteAllFromRealm()
|
||||
@ -127,7 +136,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
// State event
|
||||
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
||||
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
|
||||
?: Int.MIN_VALUE
|
||||
?: Int.MIN_VALUE
|
||||
val untimelinedStateIndex = minStateIndex + 1
|
||||
roomSync.state.events.forEach { event ->
|
||||
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
||||
@ -150,14 +159,6 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
roomEntity.addOrUpdate(chunkEntity)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -167,7 +168,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
InvitedRoomSync): RoomEntity {
|
||||
Timber.v("Handle invited sync for room $roomId")
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||
?: realm.createObject(roomId)
|
||||
?: realm.createObject(roomId)
|
||||
roomEntity.membership = Membership.INVITE
|
||||
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
|
||||
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
|
||||
@ -181,7 +182,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
roomId: String,
|
||||
roomSync: RoomSync): RoomEntity {
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
||||
?: realm.createObject(roomId)
|
||||
?: realm.createObject(roomId)
|
||||
|
||||
roomEntity.membership = Membership.LEAVE
|
||||
roomEntity.chunks.deleteAllFromRealm()
|
||||
@ -233,17 +234,21 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun handleEphemeral(realm: Realm,
|
||||
roomId: String,
|
||||
ephemeral: RoomSyncEphemeral) {
|
||||
ephemeral.events
|
||||
.filter { it.getClearType() == EventType.RECEIPT }
|
||||
.map { it.content.toModel<ReadReceiptContent>() }
|
||||
.forEach { readReceiptHandler.handle(realm, roomId, it) }
|
||||
ephemeral: RoomSyncEphemeral,
|
||||
isInitalSync: Boolean) {
|
||||
for (event in ephemeral.events) {
|
||||
if (event.type != EventType.RECEIPT) continue
|
||||
val readReceiptContent = event.content as? ReadReceiptContent ?: continue
|
||||
readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitalSync)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
||||
accountData.events
|
||||
.asSequence()
|
||||
.filter { it.getClearType() == EventType.TAG }
|
||||
.map { it.content.toModel<RoomTagContent>() }
|
||||
.forEach { roomTagHandler.handle(realm, roomId, it) }
|
||||
|
@ -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) {
|
||||
if (syncResponse.rooms != null) {
|
||||
roomSyncHandler.handle(syncResponse.rooms, reporter)
|
||||
roomSyncHandler.handle(syncResponse.rooms, isInitialSync, reporter)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.TaskThread
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import timber.log.Timber
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
@ -70,6 +71,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||
if (state is SyncState.RUNNING) {
|
||||
Timber.v("Pause sync...")
|
||||
updateStateTo(SyncState.PAUSED)
|
||||
cancelableTask?.cancel()
|
||||
lock.notify()
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,18 +93,25 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||
backgroundDetectionObserver.register(this)
|
||||
|
||||
while (state != SyncState.KILLING) {
|
||||
Timber.v("Entering loop, state: $state")
|
||||
|
||||
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
|
||||
Timber.v("Sync is Paused. Waiting...")
|
||||
Timber.v("No network or sync is Paused. Waiting...")
|
||||
synchronized(lock) {
|
||||
lock.wait()
|
||||
}
|
||||
Timber.v("...unlocked")
|
||||
} else {
|
||||
if (state !is SyncState.RUNNING) {
|
||||
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 params = SyncTask.Params(DEFAULT_LONG_POOL_TIMEOUT)
|
||||
val params = SyncTask.Params(timeout)
|
||||
|
||||
cancelableTask = syncTask.configureWith(params) {
|
||||
this.callbackThread = TaskThread.SYNC
|
||||
@ -109,29 +119,31 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
|
||||
override fun onSuccess(data: Unit) {
|
||||
Timber.v("onSuccess")
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
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
|
||||
Timber.v("Timeout")
|
||||
} else {
|
||||
Timber.e(failure)
|
||||
}
|
||||
|
||||
if (failure !is Failure.NetworkConnection
|
||||
|| failure.cause is JsonEncodingException) {
|
||||
// Wait 10s before retrying
|
||||
sleep(RETRY_WAIT_TIME_MS)
|
||||
}
|
||||
|
||||
if (failure is Failure.ServerError
|
||||
} 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
@ -139,8 +151,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||
.executeBy(taskExecutor)
|
||||
|
||||
latch.await()
|
||||
if (state is SyncState.RUNNING) {
|
||||
updateStateTo(SyncState.RUNNING(afterPause = false))
|
||||
state.let {
|
||||
if (it is SyncState.RUNNING && it.afterPause) {
|
||||
updateStateTo(SyncState.RUNNING(afterPause = false))
|
||||
}
|
||||
}
|
||||
|
||||
Timber.v("...Continue")
|
||||
|
@ -29,7 +29,15 @@ static def generateVersionCodeFromTimestamp() {
|
||||
}
|
||||
|
||||
def generateVersionCodeFromVersionName() {
|
||||
return versionMajor * 10000 + versionMinor * 100 + versionPatch
|
||||
return versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch
|
||||
}
|
||||
|
||||
def getVersionCode() {
|
||||
if (gitBranchName() == "develop") {
|
||||
return generateVersionCodeFromTimestamp()
|
||||
} else {
|
||||
return generateVersionCodeFromVersionName()
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
@ -47,6 +55,14 @@ static def gitBranchName() {
|
||||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
static def getVersionSuffix() {
|
||||
if (gitBranchName() == "master") {
|
||||
return ""
|
||||
} else {
|
||||
return "-dev"
|
||||
}
|
||||
}
|
||||
|
||||
project.android.buildTypes.all { buildType ->
|
||||
buildType.javaCompileOptions.annotationProcessorOptions.arguments =
|
||||
[
|
||||
@ -71,11 +87,11 @@ android {
|
||||
targetSdkVersion 28
|
||||
multiDexEnabled true
|
||||
|
||||
// For release, use generateVersionCodeFromVersionName()
|
||||
versionCode generateVersionCodeFromTimestamp()
|
||||
//versionCode generateVersionCodeFromVersionName()
|
||||
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.
|
||||
// Other branches (master, features, etc.) will have version code based on application version.
|
||||
versionCode project.getVersionCode()
|
||||
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}-dev"
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getVersionSuffix()}"
|
||||
|
||||
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
|
||||
resValue "string", "git_revision", "\"${gitRevision()}\""
|
||||
@ -117,9 +133,10 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
android.applicationVariants.all { variant ->
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -301,6 +318,8 @@ dependencies {
|
||||
|
||||
implementation 'diff_match_patch:diff_match_patch:current'
|
||||
|
||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||
|
||||
// TESTS
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package im.vector.riotx.fdroid.features.settings.troubleshoot
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
@ -25,12 +24,12 @@ import javax.inject.Inject
|
||||
/**
|
||||
* Test that the application is started on boot
|
||||
*/
|
||||
class TestAutoStartBoot @Inject constructor(private val context: AppCompatActivity,
|
||||
class TestAutoStartBoot @Inject constructor(private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider)
|
||||
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
|
||||
|
||||
override fun perform() {
|
||||
if (VectorPreferences.autoStartOnBoot(context)) {
|
||||
if (vectorPreferences.autoStartOnBoot()) {
|
||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
|
||||
status = TestStatus.SUCCESS
|
||||
quickFix = null
|
||||
@ -38,7 +37,7 @@ class TestAutoStartBoot @Inject constructor(private val context: AppCompatActivi
|
||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_failed)
|
||||
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
|
||||
override fun doFix() {
|
||||
VectorPreferences.setAutoStartOnBoot(context, true)
|
||||
vectorPreferences.setAutoStartOnBoot(true)
|
||||
manager?.retry()
|
||||
}
|
||||
}
|
||||
|
@ -63,9 +63,9 @@ object FcmHelper {
|
||||
AlarmSyncBroadcastReceiver.cancelAlarm(context)
|
||||
}
|
||||
|
||||
fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
||||
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||
//We need to use alarm in this mode
|
||||
if (VectorPreferences.areNotificationEnabledForDevice(context) && activeSessionHolder.hasActiveSession()) {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||
val currentSession = activeSessionHolder.getActiveSession()
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L)
|
||||
Timber.i("Alarm scheduled to restart service")
|
||||
|
@ -52,6 +52,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
private lateinit var notifiableEventResolver: NotifiableEventResolver
|
||||
private lateinit var pusherManager: PushersManager
|
||||
private lateinit var activeSessionHolder: ActiveSessionHolder
|
||||
private lateinit var vectorPreferences: VectorPreferences
|
||||
|
||||
// UI handler
|
||||
private val mUIHandler by lazy {
|
||||
@ -64,6 +65,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
notifiableEventResolver = vectorComponent().notifiableEventResolver()
|
||||
pusherManager = vectorComponent().pusherManager()
|
||||
activeSessionHolder = vectorComponent().activeSessionHolder()
|
||||
vectorPreferences = vectorComponent().vectorPreferences()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,7 +74,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
* @param message the message
|
||||
*/
|
||||
override fun onMessageReceived(message: RemoteMessage?) {
|
||||
if (!VectorPreferences.areNotificationEnabledForDevice(applicationContext)) {
|
||||
if (!vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
Timber.i("Notification are disabled for this device")
|
||||
return
|
||||
}
|
||||
@ -107,7 +109,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
if (refreshedToken == null) {
|
||||
Timber.w("onNewToken:received null token")
|
||||
} else {
|
||||
if (VectorPreferences.areNotificationEnabledForDevice(applicationContext) && activeSessionHolder.hasActiveSession()) {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||
pusherManager.registerPusherWithFcmKey(refreshedToken)
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import com.google.firebase.iid.FirebaseInstanceId
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.pushers.PushersManager
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@ -105,7 +106,7 @@ object FcmHelper {
|
||||
// No op
|
||||
}
|
||||
|
||||
fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
||||
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||
// TODO FCM fallback
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,13 @@ package im.vector.riotx
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import androidx.core.provider.FontRequest
|
||||
import androidx.core.provider.FontsContractCompat
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import androidx.emoji.text.FontRequestEmojiCompatConfig
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
@ -42,6 +45,7 @@ import im.vector.riotx.core.di.DaggerVectorComponent
|
||||
import im.vector.riotx.core.di.HasVectorInjector
|
||||
import im.vector.riotx.core.di.VectorComponent
|
||||
import im.vector.riotx.core.extensions.configureAndStart
|
||||
import im.vector.riotx.core.utils.initKnownEmojiHashSet
|
||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
@ -49,12 +53,12 @@ import im.vector.riotx.features.notifications.NotificationUtils
|
||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||
import im.vector.riotx.features.rageshake.VectorFileLogger
|
||||
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import im.vector.riotx.features.version.getVersion
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import im.vector.riotx.features.version.VersionProvider
|
||||
import im.vector.riotx.push.fcm.FcmHelper
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import im.vector.riotx.core.utils.initKnownEmojiHashSet
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
||||
@ -69,6 +73,8 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||
@Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
@Inject lateinit var versionProvider: VersionProvider
|
||||
lateinit var vectorComponent: VectorComponent
|
||||
private var fontThreadHandler: Handler? = null
|
||||
|
||||
@ -102,6 +108,23 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||
)
|
||||
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
||||
vectorConfiguration.initConfiguration()
|
||||
|
||||
//Use emoji compat for the benefit of emoji spans
|
||||
val config = FontRequestEmojiCompatConfig(this, fontRequest)
|
||||
.setReplaceAll(true) // we want to replace all emojis with selected font
|
||||
// .setEmojiSpanIndicatorEnabled(true)
|
||||
// .setEmojiSpanIndicatorColor(Color.GREEN)
|
||||
EmojiCompat.init(config)
|
||||
.registerInitCallback(object : EmojiCompat.InitCallback() {
|
||||
override fun onInitialized() {
|
||||
Timber.v("Emoji compat onInitialized success ")
|
||||
}
|
||||
|
||||
override fun onFailed(throwable: Throwable?) {
|
||||
Timber.e(throwable,"Failed to init EmojiCompat")
|
||||
}
|
||||
})
|
||||
|
||||
NotificationUtils.createNotificationChannels(applicationContext)
|
||||
if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
|
||||
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!
|
||||
@ -122,7 +145,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||
fun entersBackground() {
|
||||
Timber.i("App entered background") // call persistInfo
|
||||
notificationDrawerManager.persistInfo()
|
||||
FcmHelper.onEnterBackground(appContext, activeSessionHolder)
|
||||
FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder)
|
||||
}
|
||||
})
|
||||
//This should be done as early as possible
|
||||
@ -138,7 +161,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||
}
|
||||
|
||||
private fun logInfo() {
|
||||
val appVersion = getVersion(longFormat = true, useBuildNumber = true)
|
||||
val appVersion = versionProvider.getVersion(longFormat = true, useBuildNumber = true)
|
||||
val sdkVersion = Matrix.getSdkVersion()
|
||||
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
|
||||
|
||||
|
@ -14,15 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.room.detail.timeline.helper
|
||||
package im.vector.riotx.core.date
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateUtils
|
||||
import im.vector.riotx.core.resources.LocaleProvider
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class TimelineDateFormatter @Inject constructor (private val localeProvider: LocaleProvider) {
|
||||
class VectorDateFormatter @Inject constructor(private val context: Context,
|
||||
private val localeProvider: LocaleProvider) {
|
||||
|
||||
private val messageHourFormatter by lazy {
|
||||
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
||||
@ -39,4 +42,16 @@ class TimelineDateFormatter @Inject constructor (private val localeProvider: Loc
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.di
|
||||
|
||||
import com.squareup.inject.assisted.dagger2.AssistedModule
|
||||
import dagger.Module
|
||||
|
||||
@AssistedModule
|
||||
@Module(includes = [AssistedInject_AssistedInjectModule::class])
|
||||
interface AssistedInjectModule
|
@ -41,7 +41,12 @@ import im.vector.riotx.features.home.createdirect.CreateDirectRoomDirectoryUsers
|
||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomKnownUsersFragment
|
||||
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.timeline.action.*
|
||||
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||
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.list.RoomListFragment
|
||||
import im.vector.riotx.features.invite.VectorInviteView
|
||||
@ -60,12 +65,15 @@ import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||
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.VectorSettingsNotificationsTroubleshootFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
||||
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||
|
||||
@Component(dependencies = [VectorComponent::class], modules = [ViewModelModule::class, HomeModule::class])
|
||||
@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class])
|
||||
@ScreenScope
|
||||
interface ScreenComponent {
|
||||
|
||||
@ -153,6 +161,12 @@ interface ScreenComponent {
|
||||
|
||||
fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)
|
||||
|
||||
fun inject(vectorSettingsAdvancedNotificationPreferenceFragment: VectorSettingsAdvancedNotificationPreferenceFragment)
|
||||
|
||||
fun inject(vectorSettingsSecurityPrivacyFragment: VectorSettingsSecurityPrivacyFragment)
|
||||
|
||||
fun inject(vectorSettingsHelpAboutFragment: VectorSettingsHelpAboutFragment)
|
||||
|
||||
fun inject(userAvatarPreference: UserAvatarPreference)
|
||||
|
||||
fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment)
|
||||
@ -165,6 +179,8 @@ interface ScreenComponent {
|
||||
|
||||
fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
|
||||
|
||||
fun inject(displayReadReceiptsBottomSheet: DisplayReadReceiptsBottomSheet)
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(vectorComponent: VectorComponent,
|
||||
|
@ -41,6 +41,7 @@ import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||
import im.vector.riotx.features.rageshake.BugReporter
|
||||
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Component(modules = [VectorModule::class])
|
||||
@ -95,6 +96,8 @@ interface VectorComponent {
|
||||
|
||||
fun notifiableEventResolver(): NotifiableEventResolver
|
||||
|
||||
fun vectorPreferences(): VectorPreferences
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance context: Context): VectorComponent
|
||||
|
@ -25,41 +25,17 @@ import im.vector.riotx.core.platform.ConfigurationViewModel
|
||||
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.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.verification.SasVerificationViewModel
|
||||
import im.vector.riotx.features.home.*
|
||||
import im.vector.riotx.features.home.HomeNavigationViewModel
|
||||
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.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
|
||||
|
||||
@Module
|
||||
interface ViewModelModule {
|
||||
|
||||
|
||||
/**
|
||||
* ViewModels with @IntoMap will be injected by this factory
|
||||
*/
|
||||
@ -69,6 +45,7 @@ interface ViewModelModule {
|
||||
/**
|
||||
* Below are bindings for the androidx view models (which extend ViewModel). Will be converted to MvRx ViewModel in the future.
|
||||
*/
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(SignOutViewModel::class)
|
||||
@ -124,62 +101,4 @@ interface ViewModelModule {
|
||||
@ViewModelKey(CreateDirectRoomNavigationViewModel::class)
|
||||
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
|
||||
|
||||
}
|
@ -161,7 +161,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
unBinder?.unbind()
|
||||
unBinder = null
|
||||
|
||||
|
@ -16,13 +16,17 @@
|
||||
|
||||
package im.vector.riotx.core.resources
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserPreferencesProvider @Inject constructor(private val context: Context) {
|
||||
class UserPreferencesProvider @Inject constructor(private val vectorPreferences: VectorPreferences) {
|
||||
|
||||
fun shouldShowHiddenEvents(): Boolean {
|
||||
return VectorPreferences.shouldShowHiddenEvents(context)
|
||||
return vectorPreferences.shouldShowHiddenEvents()
|
||||
}
|
||||
|
||||
fun shouldShowReadReceipts(): Boolean {
|
||||
return vectorPreferences.showReadReceipts()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.core.platform
|
||||
package im.vector.riotx.core.ui.views
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
@ -32,10 +32,7 @@ import androidx.core.content.ContextCompat
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
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.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.error.ResourceLimitErrorFormatter
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -65,6 +65,7 @@ import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
|
||||
import im.vector.matrix.android.api.session.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
@ -76,7 +77,7 @@ import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.setTextOrHide
|
||||
import im.vector.riotx.core.files.addEntryToDownloadManager
|
||||
import im.vector.riotx.core.glide.GlideApp
|
||||
import im.vector.riotx.core.platform.NotificationAreaView
|
||||
import im.vector.riotx.core.ui.views.NotificationAreaView
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
import im.vector.riotx.core.utils.*
|
||||
import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter
|
||||
@ -91,6 +92,7 @@ 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.TextComposerViewModel
|
||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState
|
||||
import im.vector.riotx.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
|
||||
@ -184,6 +186,7 @@ class RoomDetailFragment :
|
||||
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
||||
private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
|
||||
@Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_room_detail
|
||||
@ -246,6 +249,14 @@ class RoomDetailFragment :
|
||||
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() {
|
||||
@ -315,17 +326,17 @@ class RoomDetailFragment :
|
||||
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
|
||||
val parser = Parser.builder().build()
|
||||
val document = parser.parse(messageContent.formattedBody
|
||||
?: messageContent.body)
|
||||
?: messageContent.body)
|
||||
formattedBody = eventHtmlRenderer.render(document)
|
||||
}
|
||||
composerLayout.composerRelatedMessageContent.text = formattedBody
|
||||
?: nonFormattedBody
|
||||
?: nonFormattedBody
|
||||
|
||||
composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "")
|
||||
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
|
||||
|
||||
avatarRenderer.render(event.senderAvatar, event.root.senderId
|
||||
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
||||
?: "", event.senderName, composerLayout.composerRelatedMessageAvatar)
|
||||
|
||||
composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text.length)
|
||||
composerLayout.expand {
|
||||
@ -354,9 +365,9 @@ class RoomDetailFragment :
|
||||
REQUEST_FILES_REQUEST_CODE, TAKE_IMAGE_REQUEST_CODE -> handleMediaIntent(data)
|
||||
REACTION_SELECT_REQUEST_CODE -> {
|
||||
val eventId = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_EVENT_ID)
|
||||
?: return
|
||||
?: return
|
||||
val reaction = data.getStringExtra(EmojiReactionPickerActivity.EXTRA_REACTION_RESULT)
|
||||
?: return
|
||||
?: return
|
||||
//TODO check if already reacted with that?
|
||||
roomDetailViewModel.process(RoomDetailActions.SendReaction(reaction, eventId))
|
||||
}
|
||||
@ -389,28 +400,28 @@ class RoomDetailFragment :
|
||||
recyclerView.setController(timelineEventController)
|
||||
timelineEventController.callback = this
|
||||
|
||||
if (VectorPreferences.swipeToReplyIsEnabled(requireContext())) {
|
||||
if (vectorPreferences.swipeToReplyIsEnabled()) {
|
||||
val swipeCallback = RoomMessageTouchHelperCallback(requireContext(),
|
||||
R.drawable.ic_reply,
|
||||
object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
||||
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
|
||||
(model as? AbsMessageItem)?.informationData?.let {
|
||||
val eventId = it.eventId
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
||||
}
|
||||
}
|
||||
R.drawable.ic_reply,
|
||||
object : RoomMessageTouchHelperCallback.QuickReplayHandler {
|
||||
override fun performQuickReplyOnHolder(model: EpoxyModel<*>) {
|
||||
(model as? AbsMessageItem)?.informationData?.let {
|
||||
val eventId = it.eventId
|
||||
roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun canSwipeModel(model: EpoxyModel<*>): Boolean {
|
||||
return when (model) {
|
||||
is MessageFileItem,
|
||||
is MessageImageVideoItem,
|
||||
is MessageTextItem -> {
|
||||
return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
})
|
||||
override fun canSwipeModel(model: EpoxyModel<*>): Boolean {
|
||||
return when (model) {
|
||||
is MessageFileItem,
|
||||
is MessageImageVideoItem,
|
||||
is MessageTextItem -> {
|
||||
return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
})
|
||||
val touchHelper = ItemTouchHelper(swipeCallback)
|
||||
touchHelper.attachToRecyclerView(recyclerView)
|
||||
}
|
||||
@ -482,7 +493,7 @@ class RoomDetailFragment :
|
||||
composerLayout.sendButton.setOnClickListener {
|
||||
val textMessage = composerLayout.composerEditText.text.toString()
|
||||
if (textMessage.isNotBlank()) {
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, VectorPreferences.isMarkdownEnabled(requireContext())))
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage, vectorPreferences.isMarkdownEnabled()))
|
||||
}
|
||||
}
|
||||
composerLayout.composerRelatedMessageCloseButton.setOnClickListener {
|
||||
@ -507,7 +518,7 @@ class RoomDetailFragment :
|
||||
items.add(DialogListItem.SendFile)
|
||||
// Send voice
|
||||
|
||||
if (VectorPreferences.isSendVoiceFeatureEnabled(this)) {
|
||||
if (vectorPreferences.isSendVoiceFeatureEnabled()) {
|
||||
items.add(DialogListItem.SendVoice.INSTANCE)
|
||||
}
|
||||
|
||||
@ -516,7 +527,7 @@ class RoomDetailFragment :
|
||||
//items.add(DialogListItem.SendSticker)
|
||||
// Camera
|
||||
|
||||
//if (VectorPreferences.useNativeCamera(this)) {
|
||||
//if (vectorPreferences.useNativeCamera()) {
|
||||
items.add(DialogListItem.TakePhoto)
|
||||
items.add(DialogListItem.TakeVideo)
|
||||
//} else {
|
||||
@ -638,8 +649,12 @@ class RoomDetailFragment :
|
||||
|
||||
private fun renderSendMessageResult(sendMessageResult: SendMessageResult) {
|
||||
when (sendMessageResult) {
|
||||
is SendMessageResult.MessageSent,
|
||||
is SendMessageResult.MessageSent -> {
|
||||
// Clear composer
|
||||
composerLayout.composerEditText.text = null
|
||||
}
|
||||
is SendMessageResult.SlashCommandHandled -> {
|
||||
sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
|
||||
// Clear composer
|
||||
composerLayout.composerEditText.text = null
|
||||
}
|
||||
@ -816,6 +831,11 @@ class RoomDetailFragment :
|
||||
})
|
||||
}
|
||||
|
||||
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {
|
||||
DisplayReadReceiptsBottomSheet.newInstance(readReceipts)
|
||||
.show(requireActivity().supportFragmentManager, "DISPLAY_READ_RECEIPTS")
|
||||
}
|
||||
|
||||
// AutocompleteUserPresenter.Callback
|
||||
|
||||
override fun onQueryUsers(query: CharSequence?) {
|
||||
@ -959,7 +979,7 @@ class RoomDetailFragment :
|
||||
// vibrate = true
|
||||
}
|
||||
|
||||
// if (vibrate && VectorPreferences.vibrateWhenMentioning(context)) {
|
||||
// if (vibrate && vectorPreferences.vibrateWhenMentioning()) {
|
||||
// val v= context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
||||
// if (v?.hasVibrator() == true) {
|
||||
// v.vibrate(100)
|
||||
|
@ -43,6 +43,7 @@ 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.tombstone.RoomTombstoneContent
|
||||
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.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.rx.rx
|
||||
@ -56,6 +57,7 @@ import im.vector.riotx.core.utils.subscribeLogError
|
||||
import im.vector.riotx.features.command.CommandParser
|
||||
import im.vector.riotx.features.command.ParsedCommand
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
@ -65,7 +67,8 @@ import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: RoomDetailViewState,
|
||||
userPreferencesProvider: UserPreferencesProvider,
|
||||
private val userPreferencesProvider: UserPreferencesProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val session: Session
|
||||
) : VectorViewModel<RoomDetailViewState>(initialState) {
|
||||
|
||||
@ -73,12 +76,13 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
private val roomId = initialState.roomId
|
||||
private val eventId = initialState.eventId
|
||||
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
||||
private val allowedTypes = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
||||
TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES
|
||||
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
||||
TimelineSettings(30, false, true, TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES, userPreferencesProvider.shouldShowReadReceipts())
|
||||
} else {
|
||||
TimelineDisplayableEvents.DISPLAYABLE_TYPES
|
||||
TimelineSettings(30, true, true, TimelineDisplayableEvents.DISPLAYABLE_TYPES, userPreferencesProvider.shouldShowReadReceipts())
|
||||
}
|
||||
private var timeline = room.createTimeline(eventId, allowedTypes)
|
||||
|
||||
private var timeline = room.createTimeline(eventId, timelineSettings)
|
||||
|
||||
// Slot to keep a pending action during permission request
|
||||
var pendingAction: RoomDetailActions? = null
|
||||
@ -101,6 +105,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
|
||||
init {
|
||||
observeSyncState()
|
||||
observeRoomSummary()
|
||||
observeEventDisplayedActions()
|
||||
observeSummaryState()
|
||||
@ -137,7 +142,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
|
||||
private fun handleTombstoneEvent(action: RoomDetailActions.HandleTombstoneEvent) {
|
||||
val tombstoneContent = action.event.getClearContent().toModel<RoomTombstoneContent>()
|
||||
?: return
|
||||
?: return
|
||||
|
||||
val roomId = tombstoneContent.replacementRoom ?: ""
|
||||
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
|
||||
@ -243,8 +248,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
}
|
||||
is ParsedCommand.SetMarkdown -> {
|
||||
// TODO
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandNotImplemented)
|
||||
vectorPreferences.setMarkdownEnabled(slashCommandResult.enable)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled(
|
||||
if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled))
|
||||
}
|
||||
is ParsedCommand.UnbanUser -> {
|
||||
// TODO
|
||||
@ -268,7 +274,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
is ParsedCommand.SendEmote -> {
|
||||
room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
}
|
||||
is ParsedCommand.ChangeTopic -> {
|
||||
handleChangeTopicSlashCommand(slashCommandResult)
|
||||
@ -283,7 +289,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
|
||||
//is original event a reply?
|
||||
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) {
|
||||
//TODO check if same content?
|
||||
room.getTimeLineEvent(inReplyTo)?.let {
|
||||
@ -292,12 +298,12 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
} else {
|
||||
val messageContent: MessageContent? =
|
||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
val existingBody = messageContent?.body ?: ""
|
||||
if (existingBody != action.text) {
|
||||
room.editTextMessage(state.sendMode.timelineEvent.root.eventId
|
||||
?: "", messageContent?.type
|
||||
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
||||
?: "", messageContent?.type
|
||||
?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown)
|
||||
} else {
|
||||
Timber.w("Same message content, do not send edition")
|
||||
}
|
||||
@ -312,7 +318,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
is SendMode.QUOTE -> {
|
||||
val messageContent: MessageContent? =
|
||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||
val textMsg = messageContent?.body
|
||||
|
||||
val finalText = legacyRiotQuoteText(textMsg, action.text)
|
||||
@ -348,8 +354,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle slash command
|
||||
|
||||
}
|
||||
|
||||
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
|
||||
@ -371,7 +375,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
|
||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
|
||||
room.updateTopic(changeTopic.topic, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
@ -385,7 +389,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
|
||||
private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) {
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled)
|
||||
_sendMessageResultLiveData.postLiveEvent(SendMessageResult.SlashCommandHandled())
|
||||
|
||||
room.invite(invite.userId, object : MatrixCallback<Unit> {
|
||||
override fun onSuccess(data: Unit) {
|
||||
@ -550,7 +554,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
} else {
|
||||
// change timeline
|
||||
timeline.dispose()
|
||||
timeline = room.createTimeline(targetEventId, allowedTypes)
|
||||
timeline = room.createTimeline(targetEventId, timelineSettings)
|
||||
timeline.start()
|
||||
|
||||
withState {
|
||||
@ -630,6 +634,17 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeSyncState() {
|
||||
session.rx()
|
||||
.liveSyncState()
|
||||
.subscribe { syncState ->
|
||||
setState {
|
||||
copy(syncState = syncState)
|
||||
}
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx().liveRoomSummary()
|
||||
.execute { async ->
|
||||
|
@ -21,9 +21,9 @@ import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
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.tombstone.RoomTombstoneContent
|
||||
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.sync.SyncState
|
||||
import im.vector.matrix.android.api.session.user.model.User
|
||||
|
||||
/**
|
||||
@ -50,7 +50,8 @@ data class RoomDetailViewState(
|
||||
val sendMode: SendMode = SendMode.REGULAR,
|
||||
val isEncrypted: Boolean = false,
|
||||
val tombstoneEvent: Event? = null,
|
||||
val tombstoneEventHandling: Async<String> = Uninitialized
|
||||
val tombstoneEventHandling: Async<String> = Uninitialized,
|
||||
val syncState: SyncState = SyncState.IDLE
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomDetailArgs) : this(roomId = args.roomId, eventId = args.eventId)
|
||||
|
@ -191,12 +191,13 @@ class RoomMessageTouchHelperCallback(private val context: Context,
|
||||
}
|
||||
|
||||
val y = (itemView.top + itemView.measuredHeight / 2).toFloat()
|
||||
//magic numbers?
|
||||
val hw = imageDrawable.intrinsicWidth / 2f
|
||||
val hh = imageDrawable.intrinsicHeight / 2f
|
||||
imageDrawable.setBounds(
|
||||
(x - convertToPx(12) * scale).toInt(),
|
||||
(y - convertToPx(11) * scale).toInt(),
|
||||
(x + convertToPx(12) * scale).toInt(),
|
||||
(y + convertToPx(10) * scale).toInt()
|
||||
(x - hw * scale).toInt(),
|
||||
(y - hh * scale).toInt(),
|
||||
(x + hw * scale).toInt(),
|
||||
(y + hh * scale).toInt()
|
||||
)
|
||||
imageDrawable.draw(canvas)
|
||||
imageDrawable.alpha = 255
|
||||
|
@ -16,13 +16,14 @@
|
||||
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import im.vector.riotx.features.command.Command
|
||||
|
||||
sealed class SendMessageResult {
|
||||
object MessageSent : SendMessageResult()
|
||||
class SlashCommandError(val command: Command) : SendMessageResult()
|
||||
class SlashCommandUnknown(val command: String) : SendMessageResult()
|
||||
object SlashCommandHandled : SendMessageResult()
|
||||
data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult()
|
||||
object SlashCommandResultOk : SendMessageResult()
|
||||
class SlashCommandResultError(val throwable: Throwable) : SendMessageResult()
|
||||
// TODO Remove
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.home.room.detail.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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.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)
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import com.airbnb.epoxy.EpoxyModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.*
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.core.epoxy.LoadingItem_
|
||||
import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.core.resources.UserPreferencesProvider
|
||||
@ -37,12 +38,13 @@ 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.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.VideoContentRenderer
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineEventController @Inject constructor(private val dateFormatter: TimelineDateFormatter,
|
||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||
private val timelineItemFactory: TimelineItemFactory,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
@ -51,7 +53,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
||||
userPreferencesProvider: UserPreferencesProvider
|
||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
|
||||
|
||||
interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback, UrlClickCallback {
|
||||
interface Callback : BaseCallback, ReactionPillCallback, AvatarCallback, UrlClickCallback, ReadReceiptsCallback {
|
||||
fun onEventVisible(event: TimelineEvent)
|
||||
fun onRoomCreateLinkClicked(url: String)
|
||||
fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View)
|
||||
@ -77,6 +79,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
||||
fun onMemberNameClicked(informationData: MessageInformationData)
|
||||
}
|
||||
|
||||
interface ReadReceiptsCallback {
|
||||
fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>)
|
||||
}
|
||||
|
||||
interface UrlClickCallback {
|
||||
fun onUrlClicked(url: String): Boolean
|
||||
fun onUrlLongClicked(url: String): Boolean
|
||||
@ -158,7 +164,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
||||
synchronized(modelCache) {
|
||||
for (i in 0 until modelCache.size) {
|
||||
if (modelCache[i]?.eventId == eventIdToHighlight
|
||||
|| modelCache[i]?.eventId == this.eventIdToHighlight) {
|
||||
|| modelCache[i]?.eventId == this.eventIdToHighlight) {
|
||||
modelCache[i] = null
|
||||
}
|
||||
}
|
||||
@ -219,8 +225,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
||||
// 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.
|
||||
if (modelCache[position] == null
|
||||
|| modelCache[position]?.mergedHeaderModel != null
|
||||
|| modelCache[position]?.formattedDayModel != null) {
|
||||
|| modelCache[position]?.mergedHeaderModel != null
|
||||
|| modelCache[position]?.formattedDayModel != null) {
|
||||
modelCache[position] = buildItemModels(position, currentSnapshot)
|
||||
}
|
||||
}
|
||||
@ -293,7 +299,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
|
||||
// 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
|
||||
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
|
||||
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey) ?: true
|
||||
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
|
||||
?: true
|
||||
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
|
||||
if (isCollapsed) {
|
||||
collapsedEventIds.addAll(mergedEventIds)
|
||||
|
@ -35,7 +35,6 @@ import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.canReact
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.core.utils.isSingleEmoji
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||
|
||||
|
||||
@ -244,7 +243,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
||||
if (event.root.getClearType() != EventType.MESSAGE) return false
|
||||
//TODO if user is admin or moderator
|
||||
return event.annotations?.reactionsSummary?.any { isSingleEmoji(it.key) } ?: false
|
||||
return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,12 +38,8 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
|
||||
@EpoxyAttribute
|
||||
var timeStamp: CharSequence? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var emojiTypeFace: Typeface? = null
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
holder.emojiReactionView.text = reactionKey
|
||||
holder.emojiReactionView.typeface = emojiTypeFace ?: Typeface.DEFAULT
|
||||
holder.displayNameView.text = authorDisplayName
|
||||
timeStamp?.let {
|
||||
holder.timeStampView.text = it
|
||||
|
@ -49,7 +49,7 @@ class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||
|
||||
private val epoxyController by lazy {
|
||||
ViewEditHistoryEpoxyController(requireContext(), viewModel.timelineDateFormatter, eventHtmlRenderer)
|
||||
ViewEditHistoryEpoxyController(requireContext(), viewModel.dateFormatter, eventHtmlRenderer)
|
||||
}
|
||||
|
||||
override fun injectWith(screenComponent: ScreenComponent) {
|
||||
|
@ -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.genericItemHeader
|
||||
import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.features.html.EventHtmlRenderer
|
||||
import me.gujun.android.span.span
|
||||
import name.fraser.neil.plaintext.diff_match_patch
|
||||
@ -44,7 +44,7 @@ import java.util.*
|
||||
* Epoxy controller for reaction event list
|
||||
*/
|
||||
class ViewEditHistoryEpoxyController(private val context: Context,
|
||||
val timelineDateFormatter: TimelineDateFormatter,
|
||||
val dateFormatter: VectorDateFormatter,
|
||||
val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController<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)) {
|
||||
//need to display header with day
|
||||
val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
|
||||
else timelineDateFormatter.formatMessageDay(timelineEvent.localDateTime())
|
||||
else dateFormatter.formatMessageDay(timelineEvent.localDateTime())
|
||||
genericItemHeader {
|
||||
id(evDate.hashCode())
|
||||
text(dateString)
|
||||
@ -113,7 +113,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
||||
when (it.operation) {
|
||||
diff_match_patch.Operation.DELETE -> {
|
||||
span {
|
||||
text = it.text
|
||||
text = it.text.replace("\n"," ")
|
||||
textColor = ContextCompat.getColor(context, R.color.vector_error_color)
|
||||
textDecorationLine = "line-through"
|
||||
}
|
||||
@ -136,7 +136,7 @@ class ViewEditHistoryEpoxyController(private val context: Context,
|
||||
}
|
||||
genericItem {
|
||||
id(timelineEvent.eventId)
|
||||
title(timelineDateFormatter.formatMessageHour(timelineEvent.localDateTime()))
|
||||
title(dateFormatter.formatMessageHour(timelineEvent.localDateTime()))
|
||||
description(spannedDiff ?: body)
|
||||
}
|
||||
}
|
||||
|
@ -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.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
@ -46,7 +46,7 @@ data class ViewEditHistoryViewState(
|
||||
class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: ViewEditHistoryViewState,
|
||||
val session: Session,
|
||||
val timelineDateFormatter: TimelineDateFormatter
|
||||
val dateFormatter: VectorDateFormatter
|
||||
) : VectorViewModel<ViewEditHistoryViewState>(initialState) {
|
||||
|
||||
private val roomId = initialState.roomId
|
||||
|
@ -28,7 +28,6 @@ import com.airbnb.epoxy.EpoxyRecyclerView
|
||||
import com.airbnb.mvrx.MvRx
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
import im.vector.riotx.EmojiCompatFontProvider
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
|
||||
@ -43,13 +42,12 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class)
|
||||
|
||||
@Inject lateinit var viewReactionViewModelFactory: ViewReactionViewModel.Factory
|
||||
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
|
||||
|
||||
@BindView(R.id.bottom_sheet_display_reactions_list)
|
||||
lateinit var epoxyRecyclerView: EpoxyRecyclerView
|
||||
|
||||
private val epoxyController by lazy {
|
||||
ViewReactionsEpoxyController(requireContext(), emojiCompatFontProvider.typeface)
|
||||
ViewReactionsEpoxyController(requireContext())
|
||||
}
|
||||
|
||||
override fun injectWith(screenComponent: ScreenComponent) {
|
||||
|
@ -16,16 +16,20 @@
|
||||
|
||||
package im.vector.riotx.features.home.room.detail.timeline.action
|
||||
|
||||
import com.airbnb.mvrx.*
|
||||
import com.airbnb.mvrx.Async
|
||||
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.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.room.model.ReactionAggregatedSummary
|
||||
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.utils.isSingleEmoji
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
@ -54,13 +58,13 @@ data class ReactionInfo(
|
||||
class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: DisplayReactionsViewState,
|
||||
private val session: Session,
|
||||
private val timelineDateFormatter: TimelineDateFormatter
|
||||
private val dateFormatter: VectorDateFormatter
|
||||
) : VectorViewModel<DisplayReactionsViewState>(initialState) {
|
||||
|
||||
private val roomId = initialState.roomId
|
||||
private val eventId = initialState.eventId
|
||||
private val room = session.getRoom(roomId)
|
||||
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
|
||||
?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
|
||||
|
||||
@AssistedInject.Factory
|
||||
interface Factory {
|
||||
@ -86,7 +90,7 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
||||
.flatMapSingle { summaries ->
|
||||
Observable
|
||||
.fromIterable(summaries.reactionsSummary)
|
||||
.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
|
||||
//.filter { reactionAggregatedSummary -> isSingleEmoji(reactionAggregatedSummary.key) }
|
||||
.toReactionInfoList()
|
||||
}
|
||||
.execute {
|
||||
@ -100,14 +104,14 @@ class ViewReactionViewModel @AssistedInject constructor(@Assisted
|
||||
.fromIterable(summary.sourceEvents)
|
||||
.map {
|
||||
val event = room.getTimeLineEvent(it)
|
||||
?: throw RuntimeException("Your eventId is not valid")
|
||||
val localDate = event.root.localDateTime()
|
||||
?: throw RuntimeException("Your eventId is not valid")
|
||||
ReactionInfo(
|
||||
event.root.eventId!!,
|
||||
summary.key,
|
||||
event.root.senderId ?: "",
|
||||
event.getDisambiguatedDisplayName(),
|
||||
timelineDateFormatter.formatMessageHour(localDate)
|
||||
dateFormatter.formatRelativeDateTime(event.root.originServerTs)
|
||||
|
||||
)
|
||||
}
|
||||
}.toList()
|
||||
|
@ -18,6 +18,8 @@ package im.vector.riotx.features.home.room.detail.timeline.action
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.format.DateUtils
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
@ -29,7 +31,7 @@ import im.vector.riotx.core.ui.list.genericLoaderItem
|
||||
/**
|
||||
* Epoxy controller for reaction event list
|
||||
*/
|
||||
class ViewReactionsEpoxyController(private val context: Context, private val emojiCompatTypeface: Typeface?)
|
||||
class ViewReactionsEpoxyController(private val context: Context)
|
||||
: TypedEpoxyController<DisplayReactionsViewState>() {
|
||||
|
||||
override fun buildModels(state: DisplayReactionsViewState) {
|
||||
@ -49,9 +51,8 @@ class ViewReactionsEpoxyController(private val context: Context, private val emo
|
||||
state.mapReactionKeyToMemberList()?.forEach {
|
||||
reactionInfoSimpleItem {
|
||||
id(it.eventId)
|
||||
emojiTypeFace(emojiCompatTypeface)
|
||||
timeStamp(it.timestamp)
|
||||
reactionKey(it.reactionKey)
|
||||
reactionKey(EmojiCompat.get().process(it.reactionKey))
|
||||
authorDisplayName(it.authorName ?: it.authorId)
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,15 @@ import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||
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.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.*
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
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.getLastMessageContent
|
||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||
@ -47,7 +54,19 @@ 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.TimelineMediaSizeProvider
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.*
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.BlankItem_
|
||||
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.html.EventHtmlRenderer
|
||||
import im.vector.riotx.features.media.ImageContentRenderer
|
||||
@ -65,7 +84,7 @@ class MessageItemFactory @Inject constructor(
|
||||
private val imageContentRenderer: ImageContentRenderer,
|
||||
private val messageInformationDataFactory: MessageInformationDataFactory,
|
||||
private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder,
|
||||
private val userPreferencesProvider: UserPreferencesProvider) {
|
||||
private val noticeItemFactory: NoticeItemFactory) {
|
||||
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
@ -84,47 +103,26 @@ class MessageItemFactory @Inject constructor(
|
||||
|
||||
val messageContent: MessageContent =
|
||||
event.getLastMessageContent()
|
||||
?: //Malformed content, we should echo something on screen
|
||||
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||
?: //Malformed content, we should echo something on screen
|
||||
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))
|
||||
|
||||
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
|
||||
) {
|
||||
// ignore replace event, the targeted id is already edited
|
||||
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_()
|
||||
}
|
||||
// This is an edit event, we should it when debugging as a notice event
|
||||
return noticeItemFactory.create(event, highlight, callback)
|
||||
}
|
||||
// val all = event.root.toContent()
|
||||
// val ev = all.toModel<Event>()
|
||||
return when (messageContent) {
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback)
|
||||
is MessageTextContent -> buildTextMessageItem(event.root.sendState,
|
||||
messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback
|
||||
)
|
||||
informationData,
|
||||
highlight,
|
||||
callback)
|
||||
is MessageTextContent -> buildTextMessageItem(messageContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback)
|
||||
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback)
|
||||
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback)
|
||||
@ -144,6 +142,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.informationData(informationData)
|
||||
.highlighted(highlight)
|
||||
.avatarCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.filename(messageContent.body)
|
||||
.iconRes(R.drawable.filetype_audio)
|
||||
.reactionPillCallback(callback)
|
||||
@ -158,7 +157,7 @@ class MessageItemFactory @Inject constructor(
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +173,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.avatarCallback(callback)
|
||||
.filename(messageContent.body)
|
||||
.reactionPillCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||
.iconRes(R.drawable.filetype_attachment)
|
||||
.cellClickListener(
|
||||
@ -182,16 +182,12 @@ class MessageItemFactory @Inject constructor(
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
.clickListener(
|
||||
DebouncedClickListener(View.OnClickListener { _ ->
|
||||
callback?.onFileMessageClicked(informationData.eventId, messageContent)
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? {
|
||||
@ -229,6 +225,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.avatarCallback(callback)
|
||||
.mediaData(data)
|
||||
.reactionPillCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||
.clickListener(
|
||||
DebouncedClickListener(View.OnClickListener { view ->
|
||||
@ -240,7 +237,7 @@ class MessageItemFactory @Inject constructor(
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +250,7 @@ class MessageItemFactory @Inject constructor(
|
||||
val thumbnailData = ImageContentRenderer.Data(
|
||||
filename = messageContent.body,
|
||||
url = messageContent.videoInfo?.thumbnailFile?.url
|
||||
?: messageContent.videoInfo?.thumbnailUrl,
|
||||
?: messageContent.videoInfo?.thumbnailUrl,
|
||||
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
|
||||
height = messageContent.videoInfo?.height,
|
||||
maxHeight = maxHeight,
|
||||
@ -280,6 +277,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.avatarCallback(callback)
|
||||
.mediaData(thumbnailData)
|
||||
.reactionPillCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||
.cellClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { view ->
|
||||
@ -288,12 +286,11 @@ class MessageItemFactory @Inject constructor(
|
||||
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildTextMessageItem(sendState: SendState,
|
||||
messageContent: MessageTextContent,
|
||||
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?): MessageTextItem? {
|
||||
@ -320,6 +317,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.avatarCallback(callback)
|
||||
.urlClickCallback(callback)
|
||||
.reactionPillCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||
//click on the text
|
||||
.cellClickListener(
|
||||
@ -328,7 +326,7 @@ class MessageItemFactory @Inject constructor(
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,9 +356,9 @@ class MessageItemFactory @Inject constructor(
|
||||
//nop
|
||||
}
|
||||
},
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
editStart,
|
||||
editEnd,
|
||||
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
|
||||
return spannable
|
||||
}
|
||||
|
||||
@ -386,6 +384,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.avatarCallback(callback)
|
||||
.reactionPillCallback(callback)
|
||||
.urlClickCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||
.memberClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { view ->
|
||||
@ -397,7 +396,7 @@ class MessageItemFactory @Inject constructor(
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,6 +424,7 @@ class MessageItemFactory @Inject constructor(
|
||||
.highlighted(highlight)
|
||||
.avatarCallback(callback)
|
||||
.reactionPillCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.urlClickCallback(callback)
|
||||
.emojiTypeFace(emojiCompatFontProvider.typeface)
|
||||
.cellClickListener(
|
||||
@ -433,7 +433,7 @@ class MessageItemFactory @Inject constructor(
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,13 +446,14 @@ class MessageItemFactory @Inject constructor(
|
||||
.informationData(informationData)
|
||||
.highlighted(highlight)
|
||||
.avatarCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
.cellClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { view ->
|
||||
callback?.onEventCellClicked(informationData, null, view)
|
||||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, null, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,23 +25,18 @@ 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.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
|
||||
|
||||
class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter,
|
||||
private val avatarRenderer: AvatarRenderer) {
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val informationDataFactory: MessageInformationDataFactory) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?): NoticeItem? {
|
||||
val formattedText = eventFormatter.format(event) ?: return null
|
||||
val informationData = MessageInformationData(
|
||||
eventId = event.root.eventId ?: "?",
|
||||
senderId = event.root.senderId ?: "",
|
||||
sendState = event.root.sendState,
|
||||
avatarUrl = event.senderAvatar(),
|
||||
memberName = event.senderName(),
|
||||
showInformation = false
|
||||
)
|
||||
val informationData = informationDataFactory.create(event, null)
|
||||
|
||||
return NoticeItem_()
|
||||
.avatarRenderer(avatarRenderer)
|
||||
@ -49,6 +44,7 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
|
||||
.highlighted(highlight)
|
||||
.informationData(informationData)
|
||||
.baseCallback(callback)
|
||||
.readReceiptsCallback(callback)
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,6 +25,7 @@ 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.item.MessageInformationData
|
||||
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 javax.inject.Inject
|
||||
|
||||
@ -33,8 +34,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||
private val encryptedItemFactory: EncryptedItemFactory,
|
||||
private val noticeItemFactory: NoticeItemFactory,
|
||||
private val defaultItemFactory: DefaultItemFactory,
|
||||
private val roomCreateItemFactory: RoomCreateItemFactory,
|
||||
private val avatarRenderer: AvatarRenderer) {
|
||||
private val roomCreateItemFactory: RoomCreateItemFactory) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
@ -53,7 +53,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||
EventType.STATE_HISTORY_VISIBILITY,
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_ANSWER -> noticeItemFactory.create(event, highlight, callback)
|
||||
EventType.CALL_ANSWER,
|
||||
EventType.REACTION,
|
||||
EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback)
|
||||
// State room create
|
||||
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)
|
||||
// Crypto
|
||||
@ -70,24 +72,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||
// Unhandled event types (yet)
|
||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
EventType.STICKER -> defaultItemFactory.create(event, highlight)
|
||||
|
||||
else -> {
|
||||
//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
|
||||
)
|
||||
NoticeItem_()
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.informationData(informationData)
|
||||
.noticeText("{ \"type\": ${event.root.getClearType()} }")
|
||||
.highlighted(highlight)
|
||||
.baseCallback(callback)
|
||||
Timber.v("Type ${event.root.getClearType()} not handled")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.*
|
||||
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.riotx.R
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
@ -42,6 +41,9 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
|
||||
EventType.MESSAGE,
|
||||
EventType.REACTION,
|
||||
EventType.REDACTION -> formatDebug(timelineEvent.root)
|
||||
else -> {
|
||||
Timber.v("Type $type not handled by this formatter")
|
||||
null
|
||||
@ -66,6 +68,10 @@ 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? {
|
||||
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
|
||||
return if (!TextUtils.isEmpty(content.name)) {
|
||||
@ -90,7 +96,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
|
||||
|
||||
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
|
||||
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
|
||||
?: return null
|
||||
?: return null
|
||||
|
||||
val formattedVisibility = when (historyVisibility) {
|
||||
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
|
||||
@ -140,7 +146,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
|
||||
stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
|
||||
else ->
|
||||
stringProvider.getString(R.string.notice_display_name_changed_from,
|
||||
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
|
||||
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
|
||||
}
|
||||
displayText.append(displayNameText)
|
||||
}
|
||||
@ -167,7 +173,7 @@ class NoticeEventFormatter @Inject constructor(private val stringProvider: Strin
|
||||
when {
|
||||
eventContent.thirdPartyInvite != null ->
|
||||
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
|
||||
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
|
||||
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
|
||||
TextUtils.equals(event.stateKey, selfUserId) ->
|
||||
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
|
||||
event.stateKey.isNullOrEmpty() ->
|
||||
|
@ -56,7 +56,8 @@ fun TimelineEvent.isDisplayable(showHiddenEvent: Boolean): Boolean {
|
||||
return false
|
||||
}
|
||||
if (root.content.isNullOrEmpty()) {
|
||||
return false
|
||||
//redacted events have empty content but are displayable
|
||||
return root.unsignedData?.redactedEvent != null
|
||||
}
|
||||
//Edits should be filtered out!
|
||||
if (EventType.MESSAGE == root.type
|
||||
|
@ -32,6 +32,7 @@ import com.airbnb.epoxy.EpoxyAttribute
|
||||
import im.vector.matrix.android.api.session.room.send.SendState
|
||||
import im.vector.riotx.R
|
||||
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.DimensionUtils.dpToPx
|
||||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
@ -39,7 +40,6 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
|
||||
import im.vector.riotx.features.reactions.widget.ReactionButton
|
||||
import im.vector.riotx.features.ui.getMessageTextColor
|
||||
|
||||
|
||||
abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
|
||||
@EpoxyAttribute
|
||||
@ -69,6 +69,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
@EpoxyAttribute
|
||||
var avatarCallback: TimelineEventController.AvatarCallback? = null
|
||||
|
||||
@EpoxyAttribute
|
||||
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
|
||||
|
||||
private val _avatarClickListener = DebouncedClickListener(View.OnClickListener {
|
||||
avatarCallback?.onAvatarClicked(informationData)
|
||||
})
|
||||
@ -76,6 +79,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
avatarCallback?.onMemberNameClicked(informationData)
|
||||
})
|
||||
|
||||
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
|
||||
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts)
|
||||
})
|
||||
|
||||
var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
|
||||
override fun onReacted(reactionButton: ReactionButton) {
|
||||
@ -123,6 +129,8 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
holder.memberNameView.setOnLongClickListener(null)
|
||||
}
|
||||
|
||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||
|
||||
if (!shouldShowReactionAtBottom() || informationData.orderedReactionList.isNullOrEmpty()) {
|
||||
holder.reactionWrapper?.isVisible = false
|
||||
} else {
|
||||
@ -143,7 +151,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
idToRefInFlow.add(reactionButton.id)
|
||||
reactionButton.reactionString = reaction.key
|
||||
reactionButton.reactionCount = reaction.count
|
||||
reactionButton.emojiTypeFace = emojiTypeFace
|
||||
//reactionButton.emojiTypeFace = emojiTypeFace
|
||||
reactionButton.setChecked(reaction.addedByMe)
|
||||
reactionButton.isEnabled = reaction.synced
|
||||
}
|
||||
@ -173,7 +181,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
|
||||
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||
var reactionWrapper: ViewGroup? = null
|
||||
var reactionFlowHelper: Flow? = null
|
||||
}
|
||||
|
@ -32,7 +32,8 @@ data class MessageInformationData(
|
||||
/*List of reactions (emoji,count,isSelected)*/
|
||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||
val hasBeenEdited: Boolean = false,
|
||||
val hasPendingEdits: Boolean = false
|
||||
val hasPendingEdits: Boolean = false,
|
||||
val readReceipts: List<ReadReceiptData> = emptyList()
|
||||
) : Parcelable
|
||||
|
||||
|
||||
@ -43,3 +44,11 @@ data class ReactionInfoData(
|
||||
val addedByMe: Boolean,
|
||||
val synced: Boolean
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
data class ReadReceiptData(
|
||||
val userId: String,
|
||||
val avatarUrl: String?,
|
||||
val displayName: String?,
|
||||
val timestamp: Long
|
||||
) : Parcelable
|
@ -22,6 +22,8 @@ import android.widget.TextView
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
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.room.detail.timeline.TimelineEventController
|
||||
|
||||
@ -44,6 +46,13 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
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) {
|
||||
super.bind(holder)
|
||||
holder.noticeTextView.text = noticeText
|
||||
@ -55,6 +64,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
holder.avatarImageView
|
||||
)
|
||||
holder.view.setOnLongClickListener(longClickListener)
|
||||
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener)
|
||||
}
|
||||
|
||||
override fun getViewType() = STUB_ID
|
||||
@ -62,6 +72,7 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
|
||||
class Holder : BaseHolder(STUB_ID) {
|
||||
val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
|
||||
val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
|
||||
val readReceiptsView by bind<ReadReceiptsView>(R.id.readReceiptsView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
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.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited
|
||||
@ -23,16 +24,18 @@ import im.vector.riotx.core.extensions.localDateTime
|
||||
import im.vector.riotx.core.resources.ColorProvider
|
||||
import im.vector.riotx.core.utils.isSingleEmoji
|
||||
import im.vector.riotx.features.home.getColorFromUserId
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
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.ReadReceiptData
|
||||
import me.gujun.android.span.span
|
||||
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
|
||||
*/
|
||||
class MessageInformationDataFactory @Inject constructor(private val timelineDateFormatter: TimelineDateFormatter,
|
||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val colorProvider: ColorProvider) {
|
||||
|
||||
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
||||
@ -43,21 +46,20 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||
?: false
|
||||
?: false
|
||||
|
||||
val showInformation =
|
||||
addDaySeparator
|
||||
|| event.senderAvatar != nextEvent?.senderAvatar
|
||||
|| 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
|
||||
|
||||
val time = timelineDateFormatter.formatMessageHour(date)
|
||||
val time = dateFormatter.formatMessageHour(date)
|
||||
val avatarUrl = event.senderAvatar
|
||||
val memberName = event.getDisambiguatedDisplayName()
|
||||
val formattedMemberName = span(memberName) {
|
||||
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId
|
||||
?: ""))
|
||||
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId ?: ""))
|
||||
}
|
||||
|
||||
return MessageInformationData(
|
||||
@ -69,12 +71,21 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate
|
||||
memberName = formattedMemberName,
|
||||
showInformation = showInformation,
|
||||
orderedReactionList = event.annotations?.reactionsSummary
|
||||
?.filter { isSingleEmoji(it.key) }
|
||||
//?.filter { isSingleEmoji(it.key) }
|
||||
?.map {
|
||||
ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
|
||||
},
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
@ -121,7 +121,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O
|
||||
|
||||
when (newState) {
|
||||
RecyclerView.SCROLL_STATE_IDLE -> {
|
||||
createChatFabMenu.postDelayed(showFabRunnable, 1000)
|
||||
createChatFabMenu.postDelayed(showFabRunnable, 250)
|
||||
}
|
||||
RecyclerView.SCROLL_STATE_DRAGGING,
|
||||
RecyclerView.SCROLL_STATE_SETTLING -> {
|
||||
|
@ -29,13 +29,13 @@ import im.vector.riotx.core.resources.DateProvider
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
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.helper.TimelineDateFormatter
|
||||
import im.vector.riotx.core.date.VectorDateFormatter
|
||||
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
|
||||
import me.gujun.android.span.span
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
|
||||
private val timelineDateFormatter: TimelineDateFormatter,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val colorProvider: ColorProvider,
|
||||
private val stringProvider: StringProvider,
|
||||
private val avatarRenderer: AvatarRenderer) {
|
||||
@ -94,7 +94,7 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
||||
val currentDate = DateProvider.currentLocalDateTime()
|
||||
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
||||
latestFormattedEvent = if (latestEvent.root.isEncrypted()
|
||||
&& latestEvent.root.mxDecryptionResult == null) {
|
||||
&& latestEvent.root.mxDecryptionResult == null) {
|
||||
stringProvider.getString(R.string.encrypted_message)
|
||||
} else if (latestEvent.root.getClearType() == EventType.MESSAGE) {
|
||||
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
|
||||
@ -117,10 +117,9 @@ class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatte
|
||||
}
|
||||
}
|
||||
latestEventTime = if (isSameDay) {
|
||||
timelineDateFormatter.formatMessageHour(date)
|
||||
dateFormatter.formatMessageHour(date)
|
||||
} else {
|
||||
//TODO: change this
|
||||
timelineDateFormatter.formatMessageDay(date)
|
||||
dateFormatter.formatMessageDay(date)
|
||||
}
|
||||
}
|
||||
return RoomSummaryItem_()
|
||||
|
@ -89,18 +89,44 @@ class VideoContentRenderer @Inject constructor(private val activeSessionHolder:
|
||||
})
|
||||
}
|
||||
} else {
|
||||
thumbnailView.isVisible = false
|
||||
loadingView.isVisible = false
|
||||
|
||||
val resolvedUrl = contentUrlResolver.resolveFullSize(data.url)
|
||||
|
||||
if (resolvedUrl == null) {
|
||||
thumbnailView.isVisible = false
|
||||
loadingView.isVisible = false
|
||||
errorView.isVisible = true
|
||||
errorView.setText(R.string.unknown_error)
|
||||
} else {
|
||||
videoView.isVisible = true
|
||||
videoView.setVideoPath(resolvedUrl)
|
||||
videoView.start()
|
||||
|
||||
//Temporary code, some remote videos are not played by videoview setVideoUri
|
||||
//So for now we download them then play
|
||||
thumbnailView.isVisible = true
|
||||
loadingView.isVisible = true
|
||||
|
||||
activeSessionHolder.getActiveSession()
|
||||
.downloadFile(
|
||||
FileService.DownloadMode.FOR_INTERNAL_USE,
|
||||
data.eventId,
|
||||
data.filename,
|
||||
data.url,
|
||||
null,
|
||||
object : MatrixCallback<File> {
|
||||
override fun onSuccess(data: File) {
|
||||
thumbnailView.isVisible = false
|
||||
loadingView.isVisible = false
|
||||
videoView.isVisible = true
|
||||
|
||||
videoView.setVideoPath(data.path)
|
||||
videoView.start()
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
loadingView.isVisible = false
|
||||
errorView.isVisible = true
|
||||
errorView.text = errorFormatter.toHumanReadable(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import javax.inject.Singleton
|
||||
*/
|
||||
@Singleton
|
||||
class NotificationDrawerManager @Inject constructor(private val context: Context,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val iconLoader: IconLoader,
|
||||
private val bitmapLoader: BitmapLoader,
|
||||
@ -73,7 +74,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||
Events might be grouped and there might not be one notification per event!
|
||||
*/
|
||||
fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
|
||||
if (!VectorPreferences.areNotificationEnabledForDevice(context)) {
|
||||
if (!vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
Timber.i("Notification are disabled for this device")
|
||||
return
|
||||
}
|
||||
@ -326,7 +327,13 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||
globalLastMessageTimestamp = lastMessageTimestamp
|
||||
}
|
||||
|
||||
NotificationUtils.buildMessagesListNotification(context, style, roomEventGroupInfo, largeBitmap, lastMessageTimestamp, myUserDisplayName)
|
||||
NotificationUtils.buildMessagesListNotification(context,
|
||||
vectorPreferences,
|
||||
style,
|
||||
roomEventGroupInfo,
|
||||
largeBitmap,
|
||||
lastMessageTimestamp,
|
||||
myUserDisplayName)
|
||||
?.let {
|
||||
//is there an id for this room?
|
||||
notifications.add(it)
|
||||
@ -344,7 +351,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||
for (event in simpleEvents) {
|
||||
//We build a simple event
|
||||
if (firstTime || !event.hasBeenDisplayed) {
|
||||
NotificationUtils.buildSimpleEventNotification(context, event, null, session.myUserId)?.let {
|
||||
NotificationUtils.buildSimpleEventNotification(context, vectorPreferences, event, null, session.myUserId)?.let {
|
||||
notifications.add(it)
|
||||
NotificationUtils.showNotificationMessage(context, event.eventId, ROOM_EVENT_NOTIFICATION_ID, it)
|
||||
event.hasBeenDisplayed = true //we can consider it as displayed
|
||||
@ -383,6 +390,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
|
||||
|
||||
NotificationUtils.buildSummaryListNotification(
|
||||
context,
|
||||
vectorPreferences,
|
||||
summaryInboxStyle,
|
||||
sumTitle,
|
||||
noisy = hasNewEvent && summaryIsNoisy,
|
||||
|
@ -367,6 +367,7 @@ object NotificationUtils {
|
||||
* Build a notification for a Room
|
||||
*/
|
||||
fun buildMessagesListNotification(context: Context,
|
||||
vectorPreferences: VectorPreferences,
|
||||
messageStyle: NotificationCompat.MessagingStyle,
|
||||
roomInfo: RoomEventGroupInfo,
|
||||
largeIcon: Bitmap?,
|
||||
@ -420,7 +421,7 @@ object NotificationUtils {
|
||||
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||
if (roomInfo.shouldBing) {
|
||||
//Compat
|
||||
VectorPreferences.getNotificationRingTone(context)?.let {
|
||||
vectorPreferences.getNotificationRingTone()?.let {
|
||||
setSound(it)
|
||||
}
|
||||
setLights(accentColor, 500, 500)
|
||||
@ -476,7 +477,11 @@ object NotificationUtils {
|
||||
}
|
||||
|
||||
|
||||
fun buildSimpleEventNotification(context: Context, simpleNotifiableEvent: NotifiableEvent, largeIcon: Bitmap?, matrixId: String): Notification? {
|
||||
fun buildSimpleEventNotification(context: Context,
|
||||
vectorPreferences: VectorPreferences,
|
||||
simpleNotifiableEvent: NotifiableEvent,
|
||||
largeIcon: Bitmap?,
|
||||
matrixId: String): Notification? {
|
||||
val accentColor = ContextCompat.getColor(context, R.color.notification_accent_color)
|
||||
// Build the pending intent for when the notification is clicked
|
||||
val smallIcon = R.drawable.ic_status_bar
|
||||
@ -534,7 +539,7 @@ object NotificationUtils {
|
||||
if (simpleNotifiableEvent.noisy) {
|
||||
//Compat
|
||||
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||
VectorPreferences.getNotificationRingTone(context)?.let {
|
||||
vectorPreferences.getNotificationRingTone()?.let {
|
||||
setSound(it)
|
||||
}
|
||||
setLights(accentColor, 500, 500)
|
||||
@ -606,6 +611,7 @@ object NotificationUtils {
|
||||
* Build the summary notification
|
||||
*/
|
||||
fun buildSummaryListNotification(context: Context,
|
||||
vectorPreferences: VectorPreferences,
|
||||
style: NotificationCompat.InboxStyle,
|
||||
compatSummary: String,
|
||||
noisy: Boolean,
|
||||
@ -630,7 +636,7 @@ object NotificationUtils {
|
||||
if (noisy) {
|
||||
//Compat
|
||||
priority = NotificationCompat.PRIORITY_DEFAULT
|
||||
VectorPreferences.getNotificationRingTone(context)?.let {
|
||||
vectorPreferences.getNotificationRingTone()?.let {
|
||||
setSound(it)
|
||||
}
|
||||
setLights(accentColor, 500, 500)
|
||||
|
@ -35,7 +35,7 @@ import im.vector.riotx.core.extensions.toOnOff
|
||||
import im.vector.riotx.core.utils.getDeviceLocale
|
||||
import im.vector.riotx.features.settings.VectorLocale
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import im.vector.riotx.features.version.getVersion
|
||||
import im.vector.riotx.features.version.VersionProvider
|
||||
import okhttp3.*
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
@ -51,7 +51,8 @@ import javax.inject.Singleton
|
||||
* BugReporter creates and sends the bug reports.
|
||||
*/
|
||||
@Singleton
|
||||
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
|
||||
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val versionProvider: VersionProvider) {
|
||||
var inMultiWindowMode = false
|
||||
|
||||
companion object {
|
||||
@ -225,7 +226,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
.addFormDataPart("user_agent", Matrix.getInstance(context).getUserAgent())
|
||||
.addFormDataPart("user_id", userId)
|
||||
.addFormDataPart("device_id", deviceId)
|
||||
.addFormDataPart("version", getVersion(longFormat = true, useBuildNumber = false))
|
||||
.addFormDataPart("version", versionProvider.getVersion(longFormat = true, useBuildNumber = false))
|
||||
.addFormDataPart("branch_name", context.getString(R.string.git_branch_name))
|
||||
.addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion())
|
||||
.addFormDataPart("olm_version", olmVersion)
|
||||
|
@ -21,8 +21,8 @@ import android.os.Build
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotx.BuildConfig
|
||||
import im.vector.riotx.features.version.getVersion
|
||||
import im.vector.riotx.core.resources.VersionCodeProvider
|
||||
import im.vector.riotx.features.version.VersionProvider
|
||||
import timber.log.Timber
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
@ -30,16 +30,15 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter) : Thread.UncaughtExceptionHandler {
|
||||
class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter: BugReporter,
|
||||
private val versionProvider: VersionProvider,
|
||||
private val versionCodeProvider: VersionCodeProvider) : Thread.UncaughtExceptionHandler {
|
||||
|
||||
// key to save the crash status
|
||||
companion object {
|
||||
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 lateinit var context: Context
|
||||
@ -68,9 +67,9 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
|
||||
val b = StringBuilder()
|
||||
val appName = "RiotX" // TODO Matrix.getApplicationName()
|
||||
|
||||
b.append(appName + " Build : " + BuildConfig.VERSION_CODE + "\n")
|
||||
b.append("$appName Version : $vectorVersion\n")
|
||||
b.append("SDK Version : $matrixSdkVersion\n")
|
||||
b.append(appName + " Build : " + versionCodeProvider.getVersionCode() + "\n")
|
||||
b.append("$appName Version : ${versionProvider.getVersion(longFormat = true, useBuildNumber = true)}\n")
|
||||
b.append("SDK Version : ${Matrix.getSdkVersion()}\n")
|
||||
b.append("Phone : " + Build.MODEL.trim() + " (" + Build.VERSION.INCREMENTAL + " " + Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME + ")\n")
|
||||
|
||||
b.append("Memory statuses \n")
|
||||
@ -94,14 +93,6 @@ class VectorUncaughtExceptionHandler @Inject constructor(private val bugReporter
|
||||
b.append("Thread: ")
|
||||
b.append(thread.name)
|
||||
|
||||
/*
|
||||
val a = VectorApp.getCurrentActivity()
|
||||
if (a != null) {
|
||||
b.append(", Activity:")
|
||||
b.append(a.localClassName)
|
||||
}
|
||||
*/
|
||||
|
||||
b.append(", Exception: ")
|
||||
|
||||
val sw = StringWriter()
|
||||
|
@ -35,6 +35,7 @@ import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.emoji.text.EmojiCompat
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.utils.TextUtils
|
||||
|
||||
@ -58,12 +59,6 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
|
||||
|
||||
private var reactionSelector: View? = null
|
||||
|
||||
var emojiTypeFace: Typeface? = null
|
||||
set(value) {
|
||||
field = value
|
||||
emojiView?.typeface = value ?: Typeface.DEFAULT
|
||||
}
|
||||
|
||||
private var dotsView: DotsView
|
||||
private var circleView: CircleView
|
||||
var reactedListener: ReactedListener? = null
|
||||
@ -82,7 +77,9 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
|
||||
var reactionString = "😀"
|
||||
set(value) {
|
||||
field = value
|
||||
emojiView?.text = field
|
||||
//maybe cache this for performances?
|
||||
val emojiSpanned = EmojiCompat.get().process(value)
|
||||
emojiView?.text = emojiSpanned
|
||||
}
|
||||
|
||||
private var animationScaleFactor: Float = 0.toFloat()
|
||||
@ -104,7 +101,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
|
||||
|
||||
countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount)
|
||||
|
||||
emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT
|
||||
// emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT
|
||||
|
||||
val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0)
|
||||
|
||||
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.roomdirectory.roompreview
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.transition.TransitionManager
|
||||
import com.airbnb.mvrx.args
|
||||
@ -104,7 +105,12 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {
|
||||
}
|
||||
)
|
||||
|
||||
roomPreviewNoPreviewError.setTextOrHide(errorFormatter.toHumanReadable(state.lastError))
|
||||
if (state.lastError == null) {
|
||||
roomPreviewNoPreviewError.isVisible = false
|
||||
} else {
|
||||
roomPreviewNoPreviewError.isVisible = true
|
||||
roomPreviewNoPreviewError.text = errorFormatter.toHumanReadable(state.lastError)
|
||||
}
|
||||
|
||||
if (state.roomJoinState == JoinState.JOINED) {
|
||||
// Quit this screen
|
||||
|
@ -31,187 +31,192 @@ import im.vector.riotx.features.themes.ThemeUtils
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
object VectorPreferences {
|
||||
class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
|
||||
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_VERSION_PREFERENCE_KEY = "SETTINGS_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_LOGGED_IN_PREFERENCE_KEY = "SETTINGS_LOGGED_IN_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_APP_TERM_CONDITIONS_PREFERENCE_KEY = "SETTINGS_APP_TERM_CONDITIONS_PREFERENCE_KEY"
|
||||
const val SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY = "SETTINGS_PRIVACY_POLICY_PREFERENCE_KEY"
|
||||
companion object {
|
||||
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_VERSION_PREFERENCE_KEY = "SETTINGS_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_LOGGED_IN_PREFERENCE_KEY = "SETTINGS_LOGGED_IN_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_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_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY = "SETTINGS_NOTIFICATION_TROUBLESHOOT_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_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_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_USER_SETTINGS_PREFERENCE_KEY = "SETTINGS_USER_SETTINGS_PREFERENCE_KEY"
|
||||
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_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_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_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_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_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_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_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_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_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_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_INFORMATION_DEVICE_KEY_PREFERENCE_KEY = "SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_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_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_COPYRIGHT_PREFERENCE_KEY = "SETTINGS_COPYRIGHT_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_USER_SETTINGS_PREFERENCE_KEY = "SETTINGS_USER_SETTINGS_PREFERENCE_KEY"
|
||||
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_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_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_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_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_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_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_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_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_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_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_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
|
||||
const val SETTINGS_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_DISPLAY_NAME_PREFERENCE_KEY"
|
||||
const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY"
|
||||
// user
|
||||
const val SETTINGS_DISPLAY_NAME_PREFERENCE_KEY = "SETTINGS_DISPLAY_NAME_PREFERENCE_KEY"
|
||||
const val SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY = "SETTINGS_PROFILE_PICTURE_PREFERENCE_KEY"
|
||||
|
||||
// contacts
|
||||
const val SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY = "SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY"
|
||||
// contacts
|
||||
const val SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY = "SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY"
|
||||
|
||||
// interface
|
||||
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_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_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_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_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_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_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER"
|
||||
// interface
|
||||
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_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_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_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_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_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_SEND_MESSAGE_WITH_ENTER = "SETTINGS_SEND_MESSAGE_WITH_ENTER"
|
||||
|
||||
// home
|
||||
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"
|
||||
// home
|
||||
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"
|
||||
|
||||
// flair
|
||||
const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY"
|
||||
// flair
|
||||
const val SETTINGS_GROUPS_FLAIR_KEY = "SETTINGS_GROUPS_FLAIR_KEY"
|
||||
|
||||
// notifications
|
||||
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_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";
|
||||
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_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_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_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_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_CALL_INVITATIONS_PREFERENCE_KEY = "SETTINGS_CALL_INVITATIONS_PREFERENCE_KEY_2"
|
||||
// notifications
|
||||
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_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";
|
||||
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_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_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_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_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_CALL_INVITATIONS_PREFERENCE_KEY = "SETTINGS_CALL_INVITATIONS_PREFERENCE_KEY_2"
|
||||
|
||||
// media
|
||||
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_PREVIEW_MEDIA_BEFORE_SENDING_KEY = "SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY"
|
||||
private const val SETTINGS_PLAY_SHUTTER_SOUND_KEY = "SETTINGS_PLAY_SHUTTER_SOUND_KEY"
|
||||
// media
|
||||
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_PREVIEW_MEDIA_BEFORE_SENDING_KEY = "SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY"
|
||||
private const val SETTINGS_PLAY_SHUTTER_SOUND_KEY = "SETTINGS_PLAY_SHUTTER_SOUND_KEY"
|
||||
|
||||
// background sync
|
||||
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_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"
|
||||
// background sync
|
||||
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_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"
|
||||
|
||||
// Calls
|
||||
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"
|
||||
// Calls
|
||||
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"
|
||||
|
||||
// labs
|
||||
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_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_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"
|
||||
// labs
|
||||
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_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_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_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_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"
|
||||
|
||||
// analytics
|
||||
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
|
||||
const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
|
||||
// analytics
|
||||
const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY"
|
||||
const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
|
||||
|
||||
// other
|
||||
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 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_ASK_TO_USE_ANALYTICS_TRACKING_KEY = "DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY"
|
||||
const val SETTINGS_DEACTIVATE_ACCOUNT_KEY = "SETTINGS_DEACTIVATE_ACCOUNT_KEY"
|
||||
private const val SETTINGS_DISPLAY_ALL_EVENTS_KEY = "SETTINGS_DISPLAY_ALL_EVENTS_KEY"
|
||||
// other
|
||||
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 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_ASK_TO_USE_ANALYTICS_TRACKING_KEY = "DID_ASK_TO_USE_ANALYTICS_TRACKING_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 MEDIA_SAVING_3_DAYS = 0
|
||||
private const val MEDIA_SAVING_1_WEEK = 1
|
||||
private const val MEDIA_SAVING_1_MONTH = 2
|
||||
private const val MEDIA_SAVING_FOREVER = 3
|
||||
private const val MEDIA_SAVING_3_DAYS = 0
|
||||
private const val MEDIA_SAVING_1_WEEK = 1
|
||||
private const val MEDIA_SAVING_1_MONTH = 2
|
||||
private const val MEDIA_SAVING_FOREVER = 3
|
||||
|
||||
// some preferences keys must be kept after a logout
|
||||
private val mKeysToKeepAfterLogout = Arrays.asList(
|
||||
SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY,
|
||||
SETTINGS_DEFAULT_MEDIA_SOURCE_KEY,
|
||||
SETTINGS_PLAY_SHUTTER_SOUND_KEY,
|
||||
// some preferences keys must be kept after a logout
|
||||
private val mKeysToKeepAfterLogout = Arrays.asList(
|
||||
SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY,
|
||||
SETTINGS_DEFAULT_MEDIA_SOURCE_KEY,
|
||||
SETTINGS_PLAY_SHUTTER_SOUND_KEY,
|
||||
|
||||
SETTINGS_SEND_TYPING_NOTIF_KEY,
|
||||
SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY,
|
||||
SETTINGS_12_24_TIMESTAMPS_KEY,
|
||||
SETTINGS_SHOW_READ_RECEIPTS_KEY,
|
||||
SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY,
|
||||
SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY,
|
||||
SETTINGS_MEDIA_SAVING_PERIOD_KEY,
|
||||
SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY,
|
||||
SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY,
|
||||
SETTINGS_SEND_MESSAGE_WITH_ENTER,
|
||||
SETTINGS_SEND_TYPING_NOTIF_KEY,
|
||||
SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY,
|
||||
SETTINGS_12_24_TIMESTAMPS_KEY,
|
||||
SETTINGS_SHOW_READ_RECEIPTS_KEY,
|
||||
SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY,
|
||||
SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY,
|
||||
SETTINGS_MEDIA_SAVING_PERIOD_KEY,
|
||||
SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY,
|
||||
SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY,
|
||||
SETTINGS_SEND_MESSAGE_WITH_ENTER,
|
||||
|
||||
SETTINGS_PIN_UNREAD_MESSAGES_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
|
||||
SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY,
|
||||
SETTINGS_START_ON_BOOT_PREFERENCE_KEY,
|
||||
SETTINGS_INTERFACE_TEXT_SIZE_KEY,
|
||||
SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY,
|
||||
SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY,
|
||||
SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY,
|
||||
SETTINGS_PIN_UNREAD_MESSAGES_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
|
||||
SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY,
|
||||
SETTINGS_START_ON_BOOT_PREFERENCE_KEY,
|
||||
SETTINGS_INTERFACE_TEXT_SIZE_KEY,
|
||||
SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY,
|
||||
SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY,
|
||||
SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY,
|
||||
|
||||
SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY,
|
||||
SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY,
|
||||
SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY,
|
||||
SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY,
|
||||
SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY,
|
||||
SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY,
|
||||
SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY,
|
||||
SETTINGS_ROOM_SETTINGS_LABS_END_TO_END_PREFERENCE_KEY,
|
||||
SETTINGS_CONTACTS_PHONEBOOK_COUNTRY_PREFERENCE_KEY,
|
||||
SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY,
|
||||
SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY,
|
||||
SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY,
|
||||
SETTINGS_SET_SYNC_TIMEOUT_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.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
fun clearPreferences(context: Context) {
|
||||
fun clearPreferences() {
|
||||
val keysToKeep = HashSet(mKeysToKeepAfterLogout)
|
||||
|
||||
// home server urls
|
||||
@ -221,37 +226,35 @@ object VectorPreferences {
|
||||
// theme
|
||||
keysToKeep.add(ThemeUtils.APPLICATION_THEME_KEY)
|
||||
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
preferences.edit {
|
||||
// get all the existing keys
|
||||
val keys = preferences.all.keys
|
||||
// remove the one to keep
|
||||
// get all the existing keys
|
||||
val keys = defaultPrefs.all.keys
|
||||
|
||||
keys.removeAll(keysToKeep)
|
||||
// remove the one to keep
|
||||
keys.removeAll(keysToKeep)
|
||||
|
||||
defaultPrefs.edit {
|
||||
for (key in keys) {
|
||||
remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun areNotificationEnabledForDevice(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, true)
|
||||
fun areNotificationEnabledForDevice(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
fun setNotificationEnabledForDevice(context: Context, enabled: Boolean?) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, enabled!!)
|
||||
}
|
||||
fun setNotificationEnabledForDevice(enabled: Boolean?) {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY, enabled!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldShowHiddenEvents(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false)
|
||||
fun shouldShowHiddenEvents(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false)
|
||||
}
|
||||
|
||||
fun swipeToReplyIsEnabled(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true)
|
||||
fun swipeToReplyIsEnabled(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -260,8 +263,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if it was already requested
|
||||
*/
|
||||
fun didAskUserToIgnoreBatteryOptimizations(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, false)
|
||||
fun didAskUserToIgnoreBatteryOptimizations(): Boolean {
|
||||
return defaultPrefs.getBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -269,22 +272,20 @@ object VectorPreferences {
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
fun setDidAskUserToIgnoreBatteryOptimizations(context: Context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, true)
|
||||
}
|
||||
fun setDidAskUserToIgnoreBatteryOptimizations() {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(DID_ASK_TO_IGNORE_BATTERY_OPTIMIZATIONS_KEY, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun didMigrateToNotificationRework(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, false)
|
||||
fun didMigrateToNotificationRework(): Boolean {
|
||||
return defaultPrefs.getBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, false)
|
||||
}
|
||||
|
||||
fun setDidMigrateToNotificationRework(context: Context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, true)
|
||||
}
|
||||
fun setDidMigrateToNotificationRework() {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(DID_MIGRATE_TO_NOTIFICATION_REWORK, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -293,8 +294,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the time must be displayed in 12h format
|
||||
*/
|
||||
fun displayTimeIn12hFormat(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_12_24_TIMESTAMPS_KEY, false)
|
||||
fun displayTimeIn12hFormat(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_12_24_TIMESTAMPS_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,8 +304,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the join and leave membership events should be shown in the messages list
|
||||
*/
|
||||
fun showJoinLeaveMessages(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY, true)
|
||||
fun showJoinLeaveMessages(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,8 +314,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true true if the avatar and display name events should be shown in the messages list.
|
||||
*/
|
||||
fun showAvatarDisplayNameChangeMessages(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY, true)
|
||||
fun showAvatarDisplayNameChangeMessages(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SHOW_AVATAR_DISPLAY_NAME_CHANGES_MESSAGES_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,8 +324,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to use the native camera app to record video or take photo.
|
||||
*/
|
||||
fun useNativeCamera(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY, false)
|
||||
fun useNativeCamera(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -333,8 +334,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the send voice feature is enabled.
|
||||
*/
|
||||
fun isSendVoiceFeatureEnabled(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY, false)
|
||||
fun isSendVoiceFeatureEnabled(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -343,8 +344,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the selected compression level
|
||||
*/
|
||||
fun getSelectedDefaultMediaCompressionLevel(context: Context): Int {
|
||||
return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString(SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY, "0")!!)
|
||||
fun getSelectedDefaultMediaCompressionLevel(): Int {
|
||||
return Integer.parseInt(defaultPrefs.getString(SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY, "0")!!)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -353,8 +354,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the selected media source
|
||||
*/
|
||||
fun getSelectedDefaultMediaSource(context: Context): Int {
|
||||
return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(context).getString(SETTINGS_DEFAULT_MEDIA_SOURCE_KEY, "0")!!)
|
||||
fun getSelectedDefaultMediaSource(): Int {
|
||||
return Integer.parseInt(defaultPrefs.getString(SETTINGS_DEFAULT_MEDIA_SOURCE_KEY, "0")!!)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -363,8 +364,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if shutter sound should play
|
||||
*/
|
||||
fun useShutterSound(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true)
|
||||
fun useShutterSound(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_PLAY_SHUTTER_SOUND_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,9 +374,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param uri the new notification ringtone, or null for no RingTone
|
||||
*/
|
||||
fun setNotificationRingTone(context: Context, uri: Uri?) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
|
||||
fun setNotificationRingTone(uri: Uri?) {
|
||||
defaultPrefs.edit {
|
||||
var value = ""
|
||||
|
||||
if (null != uri) {
|
||||
@ -399,8 +399,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the selected ring tone or null for no RingTone
|
||||
*/
|
||||
fun getNotificationRingTone(context: Context): Uri? {
|
||||
val url = PreferenceManager.getDefaultSharedPreferences(context).getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null)
|
||||
fun getNotificationRingTone(): Uri? {
|
||||
val url = defaultPrefs.getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null)
|
||||
|
||||
// the user selects "None"
|
||||
if (TextUtils.equals(url, "")) {
|
||||
@ -433,8 +433,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the filename or null if "None" is selected
|
||||
*/
|
||||
fun getNotificationRingToneName(context: Context): String? {
|
||||
val toneUri = getNotificationRingTone(context) ?: return null
|
||||
fun getNotificationRingToneName(): String? {
|
||||
val toneUri = getNotificationRingTone() ?: return null
|
||||
|
||||
var name: String? = null
|
||||
|
||||
@ -467,11 +467,10 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param newValue true to enable lazy loading, false to disable it
|
||||
*/
|
||||
fun setUseLazyLoading(context: Context, newValue: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, newValue)
|
||||
}
|
||||
fun setUseLazyLoading(newValue: Boolean) {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -480,8 +479,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the lazy loading of room members is enabled
|
||||
*/
|
||||
fun useLazyLoading(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, false)
|
||||
fun useLazyLoading(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_LAZY_LOADING_PREFERENCE_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -489,11 +488,10 @@ object VectorPreferences {
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
fun setUserRefuseLazyLoading(context: Context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, true)
|
||||
}
|
||||
fun setUserRefuseLazyLoading() {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -502,8 +500,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the user has explicitly refuse the lazy loading of room members
|
||||
*/
|
||||
fun hasUserRefusedLazyLoading(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, false)
|
||||
fun hasUserRefusedLazyLoading(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_USER_REFUSED_LAZY_LOADING_PREFERENCE_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -512,8 +510,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the data save mode is enabled
|
||||
*/
|
||||
fun useDataSaveMode(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY, false)
|
||||
fun useDataSaveMode(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_DATA_SAVE_MODE_PREFERENCE_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -522,8 +520,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the conference call must be done with jitsi.
|
||||
*/
|
||||
fun useJitsiConfCall(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY, true)
|
||||
fun useJitsiConfCall(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_USE_JITSI_CONF_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -532,8 +530,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the application must be started on boot
|
||||
*/
|
||||
fun autoStartOnBoot(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, true)
|
||||
fun autoStartOnBoot(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -542,11 +540,10 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param value true to start the application on boot
|
||||
*/
|
||||
fun setAutoStartOnBoot(context: Context, value: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, value)
|
||||
}
|
||||
fun setAutoStartOnBoot(value: Boolean) {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_START_ON_BOOT_PREFERENCE_KEY, value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -555,8 +552,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the selected period
|
||||
*/
|
||||
fun getSelectedMediasSavingPeriod(context: Context): Int {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, MEDIA_SAVING_1_WEEK)
|
||||
fun getSelectedMediasSavingPeriod(): Int {
|
||||
return defaultPrefs.getInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, MEDIA_SAVING_1_WEEK)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -565,11 +562,10 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param index the selected period index
|
||||
*/
|
||||
fun setSelectedMediasSavingPeriod(context: Context, index: Int) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, index)
|
||||
}
|
||||
fun setSelectedMediasSavingPeriod(index: Int) {
|
||||
defaultPrefs.edit {
|
||||
putInt(SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY, index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -578,8 +574,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the min last access time (in seconds)
|
||||
*/
|
||||
fun getMinMediasLastAccessTime(context: Context): Long {
|
||||
val selection = getSelectedMediasSavingPeriod(context)
|
||||
fun getMinMediasLastAccessTime(): Long {
|
||||
val selection = getSelectedMediasSavingPeriod()
|
||||
|
||||
when (selection) {
|
||||
MEDIA_SAVING_3_DAYS -> return System.currentTimeMillis() / 1000 - 3 * 24 * 60 * 60
|
||||
@ -597,8 +593,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return the selected period
|
||||
*/
|
||||
fun getSelectedMediasSavingPeriodString(context: Context): String {
|
||||
val selection = getSelectedMediasSavingPeriod(context)
|
||||
fun getSelectedMediasSavingPeriodString(): String {
|
||||
val selection = getSelectedMediasSavingPeriod()
|
||||
|
||||
when (selection) {
|
||||
MEDIA_SAVING_3_DAYS -> return context.getString(R.string.media_saving_period_3_days)
|
||||
@ -612,7 +608,7 @@ object VectorPreferences {
|
||||
/**
|
||||
* Fix some migration issues
|
||||
*/
|
||||
fun fixMigrationIssues(context: Context) {
|
||||
fun fixMigrationIssues() {
|
||||
// Nothing to do for the moment
|
||||
}
|
||||
|
||||
@ -622,8 +618,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the markdown is enabled
|
||||
*/
|
||||
fun isMarkdownEnabled(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, true)
|
||||
fun isMarkdownEnabled(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -632,11 +628,10 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param isEnabled true to enable the markdown
|
||||
*/
|
||||
fun setMarkdownEnabled(context: Context, isEnabled: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, isEnabled)
|
||||
}
|
||||
fun setMarkdownEnabled(isEnabled: Boolean) {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_ENABLE_MARKDOWN_KEY, isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -645,8 +640,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the read receipts should be shown
|
||||
*/
|
||||
fun showReadReceipts(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SHOW_READ_RECEIPTS_KEY, true)
|
||||
fun showReadReceipts(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SHOW_READ_RECEIPTS_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -655,8 +650,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the message timestamps must be always shown
|
||||
*/
|
||||
fun alwaysShowTimeStamps(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY, false)
|
||||
fun alwaysShowTimeStamps(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -665,8 +660,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to send the typing notifs
|
||||
*/
|
||||
fun sendTypingNotifs(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SEND_TYPING_NOTIF_KEY, true)
|
||||
fun sendTypingNotifs(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SEND_TYPING_NOTIF_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -675,8 +670,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to move the missed notifications to the left side
|
||||
*/
|
||||
fun pinMissedNotifications(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY, true)
|
||||
fun pinMissedNotifications(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_PIN_MISSED_NOTIFICATIONS_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -685,8 +680,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to move the unread room to the left side
|
||||
*/
|
||||
fun pinUnreadMessages(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY, true)
|
||||
fun pinUnreadMessages(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_PIN_UNREAD_MESSAGES_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -695,8 +690,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true
|
||||
*/
|
||||
fun vibrateWhenMentioning(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_VIBRATE_ON_MENTION_KEY, false)
|
||||
fun vibrateWhenMentioning(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_VIBRATE_ON_MENTION_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -705,8 +700,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if a dialog has been displayed to ask to use the analytics tracking
|
||||
*/
|
||||
fun didAskToUseAnalytics(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, false)
|
||||
fun didAskToUseAnalytics(): Boolean {
|
||||
return defaultPrefs.getBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -714,11 +709,10 @@ object VectorPreferences {
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
fun setDidAskToUseAnalytics(context: Context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, true)
|
||||
}
|
||||
fun setDidAskToUseAnalytics() {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(DID_ASK_TO_USE_ANALYTICS_TRACKING_KEY, true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -727,8 +721,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the analytics tracking is authorized
|
||||
*/
|
||||
fun useAnalytics(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_ANALYTICS_KEY, false)
|
||||
fun useAnalytics(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_USE_ANALYTICS_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -737,11 +731,10 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param useAnalytics true to enable the analytics tracking
|
||||
*/
|
||||
fun setUseAnalytics(context: Context, useAnalytics: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_USE_ANALYTICS_KEY, useAnalytics)
|
||||
}
|
||||
fun setUseAnalytics(useAnalytics: Boolean) {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_USE_ANALYTICS_KEY, useAnalytics)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -750,8 +743,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to preview media
|
||||
*/
|
||||
fun previewMediaWhenSending(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY, false)
|
||||
fun previewMediaWhenSending(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_PREVIEW_MEDIA_BEFORE_SENDING_KEY, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -760,8 +753,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to send message with enter
|
||||
*/
|
||||
fun sendMessageWithEnter(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_SEND_MESSAGE_WITH_ENTER, false)
|
||||
fun sendMessageWithEnter(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_SEND_MESSAGE_WITH_ENTER, false)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -770,8 +763,8 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true if the rage shake is used
|
||||
*/
|
||||
fun useRageshake(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true)
|
||||
fun useRageshake(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -780,11 +773,10 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @param isEnabled true to enable the rage shake
|
||||
*/
|
||||
fun setUseRageshake(context: Context, isEnabled: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled)
|
||||
}
|
||||
fun setUseRageshake(isEnabled: Boolean) {
|
||||
defaultPrefs.edit {
|
||||
putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -793,7 +785,7 @@ object VectorPreferences {
|
||||
* @param context the context
|
||||
* @return true to display all the events even the redacted ones.
|
||||
*/
|
||||
fun displayAllEvents(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false)
|
||||
fun displayAllEvents(): Boolean {
|
||||
return defaultPrefs.getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false)
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ import androidx.core.content.edit
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.extensions.withArgs
|
||||
import im.vector.riotx.core.preference.BingRule
|
||||
import im.vector.riotx.core.preference.BingRulePreference
|
||||
import im.vector.riotx.features.notifications.NotificationUtils
|
||||
import im.vector.riotx.features.notifications.supportNotificationChannels
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
@ -45,6 +47,13 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseF
|
||||
|
||||
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() {
|
||||
val callNotificationsSystemOptions = findPreference(VectorPreferences.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY)
|
||||
if (supportNotificationChannels()) {
|
||||
@ -83,13 +92,13 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseF
|
||||
if (supportNotificationChannels()) {
|
||||
ringtonePreference.isVisible = false
|
||||
} else {
|
||||
ringtonePreference.summary = VectorPreferences.getNotificationRingToneName(requireContext())
|
||||
ringtonePreference.summary = vectorPreferences.getNotificationRingToneName()
|
||||
ringtonePreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION)
|
||||
|
||||
if (null != VectorPreferences.getNotificationRingTone(requireContext())) {
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, VectorPreferences.getNotificationRingTone(requireContext()))
|
||||
if (null != vectorPreferences.getNotificationRingTone()) {
|
||||
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, vectorPreferences.getNotificationRingTone())
|
||||
}
|
||||
|
||||
startActivityForResult(intent, REQUEST_NOTIFICATION_RINGTONE)
|
||||
@ -152,13 +161,12 @@ class VectorSettingsAdvancedNotificationPreferenceFragment : VectorSettingsBaseF
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
when (requestCode) {
|
||||
REQUEST_NOTIFICATION_RINGTONE -> {
|
||||
VectorPreferences.setNotificationRingTone(requireContext(),
|
||||
data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
|
||||
vectorPreferences.setNotificationRingTone(data?.getParcelableExtra<Parcelable>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) as Uri?)
|
||||
|
||||
// test if the selected ring tone can be played
|
||||
val notificationRingToneName = VectorPreferences.getNotificationRingToneName(requireContext())
|
||||
val notificationRingToneName = vectorPreferences.getNotificationRingToneName()
|
||||
if (null != notificationRingToneName) {
|
||||
VectorPreferences.setNotificationRingTone(requireContext(), VectorPreferences.getNotificationRingTone(requireContext()))
|
||||
vectorPreferences.setNotificationRingTone(vectorPreferences.getNotificationRingTone())
|
||||
findPreference(VectorPreferences.SETTINGS_NOTIFICATION_RINGTONE_SELECTION_PREFERENCE_KEY).summary = notificationRingToneName
|
||||
}
|
||||
}
|
||||
|
@ -23,15 +23,23 @@ import androidx.preference.Preference
|
||||
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
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.displayInWebView
|
||||
import im.vector.riotx.features.version.getVersion
|
||||
import im.vector.riotx.features.version.VersionProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
override var titleRes = R.string.preference_root_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() {
|
||||
// preference to start the App info screen, to facilitate App permissions access
|
||||
findPreference(APP_INFO_LINK_PREFERENCE_KEY)
|
||||
@ -54,7 +62,7 @@ class VectorSettingsHelpAboutFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
// application version
|
||||
(findPreference(VectorPreferences.SETTINGS_VERSION_PREFERENCE_KEY)).let {
|
||||
it.summary = getVersion(longFormat = false, useBuildNumber = true)
|
||||
it.summary = versionProvider.getVersion(longFormat = false, useBuildNumber = true)
|
||||
|
||||
it.setOnPreferenceClickListener { pref ->
|
||||
copyToClipboard(requireContext(), pref.summary)
|
||||
|
@ -36,6 +36,7 @@ class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment(
|
||||
|
||||
@Inject lateinit var pushManager: PushersManager
|
||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
|
||||
override fun bindPref() {
|
||||
findPreference(VectorPreferences.SETTINGS_ENABLE_ALL_NOTIF_PREFERENCE_KEY).let { pref ->
|
||||
@ -84,7 +85,7 @@ class VectorSettingsNotificationPreferenceFragment : VectorSettingsBaseFragment(
|
||||
val switchPref = preference as SwitchPreference
|
||||
if (switchPref.isChecked) {
|
||||
FcmHelper.getFcmToken(requireContext())?.let {
|
||||
if (VectorPreferences.areNotificationEnabledForDevice(requireContext())) {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
pushManager.registerPusherWithFcmKey(it)
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
|
||||
}
|
||||
|
||||
@Inject lateinit var vectorConfiguration: VectorConfiguration
|
||||
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||
|
||||
override fun injectWith(injector: ScreenComponent) {
|
||||
injector.inject(this)
|
||||
@ -113,17 +114,17 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
// update keep medias period
|
||||
findPreference(VectorPreferences.SETTINGS_MEDIA_SAVING_PERIOD_KEY).let {
|
||||
it.summary = VectorPreferences.getSelectedMediasSavingPeriodString(requireContext())
|
||||
it.summary = vectorPreferences.getSelectedMediasSavingPeriodString()
|
||||
|
||||
it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
context?.let { context: Context ->
|
||||
AlertDialog.Builder(context)
|
||||
.setSingleChoiceItems(R.array.media_saving_choice,
|
||||
VectorPreferences.getSelectedMediasSavingPeriod(context)) { d, n ->
|
||||
VectorPreferences.setSelectedMediasSavingPeriod(context, n)
|
||||
vectorPreferences.getSelectedMediasSavingPeriod()) { d, n ->
|
||||
vectorPreferences.setSelectedMediasSavingPeriod(n)
|
||||
d.cancel()
|
||||
|
||||
it.summary = VectorPreferences.getSelectedMediasSavingPeriodString(context)
|
||||
it.summary = vectorPreferences.getSelectedMediasSavingPeriodString()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ 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.DevicesListResponse
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.dialogs.ExportKeysDialog
|
||||
import im.vector.riotx.core.intent.ExternalIntentData
|
||||
import im.vector.riotx.core.intent.analyseIntent
|
||||
@ -57,6 +58,7 @@ import timber.log.Timber
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
@ -127,6 +129,12 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
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() {
|
||||
// Push target
|
||||
refreshPushersList()
|
||||
@ -142,20 +150,20 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
// Analytics tracking management
|
||||
(findPreference(VectorPreferences.SETTINGS_USE_ANALYTICS_KEY) as SwitchPreference).let {
|
||||
// On if the analytics tracking is activated
|
||||
it.isChecked = VectorPreferences.useAnalytics(requireContext())
|
||||
it.isChecked = vectorPreferences.useAnalytics()
|
||||
|
||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
VectorPreferences.setUseAnalytics(requireContext(), newValue as Boolean)
|
||||
vectorPreferences.setUseAnalytics(newValue as Boolean)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Rageshake Management
|
||||
(findPreference(VectorPreferences.SETTINGS_USE_RAGE_SHAKE_KEY) as SwitchPreference).let {
|
||||
it.isChecked = VectorPreferences.useRageshake(requireContext())
|
||||
it.isChecked = vectorPreferences.useRageshake()
|
||||
|
||||
it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
VectorPreferences.setUseRageshake(requireContext(), newValue as Boolean)
|
||||
vectorPreferences.setUseRageshake(newValue as Boolean)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package im.vector.riotx.features.settings.troubleshoot
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import im.vector.riotx.features.settings.VectorPreferences
|
||||
@ -24,20 +23,20 @@ import javax.inject.Inject
|
||||
/**
|
||||
* Checks if notifications are enable in the system settings for this app.
|
||||
*/
|
||||
class TestDeviceSettings @Inject constructor(private val context: AppCompatActivity,
|
||||
class TestDeviceSettings @Inject constructor(private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider)
|
||||
: TroubleshootTest(R.string.settings_troubleshoot_test_device_settings_title) {
|
||||
|
||||
override fun perform() {
|
||||
|
||||
if (VectorPreferences.areNotificationEnabledForDevice(context)) {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_device_settings_success)
|
||||
quickFix = null
|
||||
status = TestStatus.SUCCESS
|
||||
} else {
|
||||
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_device_settings_quickfix) {
|
||||
override fun doFix() {
|
||||
VectorPreferences.setNotificationEnabledForDevice(context, true)
|
||||
vectorPreferences.setNotificationEnabledForDevice(true)
|
||||
manager?.retry()
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +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
|
||||
|
||||
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
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotx.features.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
|
||||
}
|
||||
}
|
@ -34,7 +34,9 @@
|
||||
android:id="@+id/videoMediaViewerThumbnailView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:scaleType="centerInside"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ProgressBar
|
||||
@ -49,6 +51,7 @@
|
||||
android:id="@+id/videoMediaViewerVideoView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
|
@ -127,6 +127,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -95,6 +95,7 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fastScrollEnabled="true"
|
||||
android:overScrollMode="always"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -9,6 +9,7 @@
|
||||
android:id="@+id/groupListEpoxyRecyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:overScrollMode="always"
|
||||
tools:listitem="@layout/item_group" />
|
||||
|
||||
</im.vector.riotx.core.platform.StateView>
|
||||
|
@ -12,6 +12,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?riotx_header_panel_background"
|
||||
android:overScrollMode="always"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/item_public_room" />
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user