forked from GitHub-Mirror/riotX-android
Compare commits
94 Commits
feature/do
...
develop
Author | SHA1 | Date | |
---|---|---|---|
|
f2c8d4ad02 | ||
|
be524472ec | ||
|
1b82a1a24d | ||
|
cf0b331c3b | ||
|
2a92a3dc80 | ||
|
012840abba | ||
|
a5975a099e | ||
|
38da4b9ee5 | ||
|
242e60fcaa | ||
|
a23be05cbf | ||
|
ed39b02924 | ||
|
fe931b5361 | ||
|
90d9cd0587 | ||
|
9cedb18921 | ||
|
e89ba7b87b | ||
|
902657c22a | ||
|
eec2abf164 | ||
|
6879cc8ca8 | ||
|
fd6bbbd3b5 | ||
|
0ff0b014a9 | ||
|
fdc9e84dd5 | ||
|
58f878fca9 | ||
|
88095e4bd9 | ||
|
47d22a3d5e | ||
|
28e82cb8ea | ||
|
35817245cb | ||
|
75266f42bb | ||
|
95c4c9ce56 | ||
|
ce5570105d | ||
|
188a9aebfa | ||
|
c95223f5d2 | ||
|
ef0362ba9c | ||
|
ea242f6737 | ||
|
cbc08d834b | ||
|
0ab6b33fb6 | ||
|
1b394527b6 | ||
|
a8f1388721 | ||
|
166be4e289 | ||
|
b49ccefe63 | ||
|
825760d17e | ||
|
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 | ||
|
456908c851 | ||
|
215324a03e | ||
|
02e342849f |
50
CHANGES.md
50
CHANGES.md
@ -1,8 +1,50 @@
|
|||||||
Changes in RiotX 0.3.0 (2019-XX-XX)
|
Changes in RiotX 0.5.0 (2019-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features:
|
||||||
|
-
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
- Reduce default release build log level, and lab option to enable more logs.
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
-
|
||||||
|
|
||||||
|
Bugfix:
|
||||||
|
- Fix crash due to missing informationData (#535)
|
||||||
|
- Progress in initial sync dialog is decreasing for a step and should not (#532)
|
||||||
|
|
||||||
|
Translations:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build:
|
||||||
|
- Fix issue with version name (#533)
|
||||||
|
- Fix rendering issue of accepted third party invitation event
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
- Regression / e2e replies not encrypted
|
||||||
|
- Some video won't play
|
||||||
|
- Privacy: remove log of notifiable event (#519)
|
||||||
|
- Fix crash with EmojiCompat (#530)
|
||||||
|
|
||||||
|
Changes in RiotX 0.3.0 (2019-08-08)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Create Direct Room flow
|
- Create Direct Room flow
|
||||||
|
- Handle `/markdown` command
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
- UI for pending edits (#193)
|
- UI for pending edits (#193)
|
||||||
@ -11,9 +53,10 @@ Improvements:
|
|||||||
- Enable proper cancellation of suspending functions (including db transaction)
|
- Enable proper cancellation of suspending functions (including db transaction)
|
||||||
- Enhances network connectivity checks in SDK
|
- Enhances network connectivity checks in SDK
|
||||||
- Add "View Edit History" item in the message bottom sheet (#401)
|
- Add "View Edit History" item in the message bottom sheet (#401)
|
||||||
|
- Cancel sync request on pause and timeout to 0 after pause (#404)
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
-
|
- Show sync progress also in room detail screen (#403)
|
||||||
|
|
||||||
Bugfix:
|
Bugfix:
|
||||||
- Edited message: link confusion when (edited) appears in body (#398)
|
- Edited message: link confusion when (edited) appears in body (#398)
|
||||||
@ -23,9 +66,6 @@ Bugfix:
|
|||||||
- Fix clear cache (#408) and Logout (#205)
|
- Fix clear cache (#408) and Logout (#205)
|
||||||
- Fix `(edited)` link can be copied to clipboard (#402)
|
- Fix `(edited)` link can be copied to clipboard (#402)
|
||||||
|
|
||||||
Translations:
|
|
||||||
-
|
|
||||||
|
|
||||||
Build:
|
Build:
|
||||||
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
- Split APK: generate one APK per arch, to reduce APK size of about 30%
|
||||||
|
|
||||||
|
@ -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.Room
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
@ -49,6 +50,10 @@ class RxRoom(private val room: Room) {
|
|||||||
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
room.join(viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||||
|
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Room.rx(): RxRoom {
|
fun Room.rx(): RxRoom {
|
||||||
|
@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
|||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import java.util.*
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal class CryptoStoreHelper {
|
internal class CryptoStoreHelper {
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createCredential() = Credentials(
|
fun createCredential() = Credentials(
|
||||||
userId = "userId_" + Random().nextInt(),
|
userId = "userId_" + Random.nextInt(),
|
||||||
homeServer = "http://matrix.org",
|
homeServer = "http://matrix.org",
|
||||||
accessToken = "access_token",
|
accessToken = "access_token",
|
||||||
refreshToken = null,
|
refreshToken = null,
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.matrix.android.api.comparators
|
package im.vector.matrix.android.api.comparators
|
||||||
|
|
||||||
import im.vector.matrix.android.api.interfaces.DatedObject
|
import im.vector.matrix.android.api.interfaces.DatedObject
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object DatedObjectComparators {
|
object DatedObjectComparators {
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.extensions
|
|||||||
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import java.util.*
|
import java.util.Collections
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* MXDeviceInfo
|
* MXDeviceInfo
|
||||||
|
@ -18,18 +18,17 @@ package im.vector.matrix.android.api.pushrules
|
|||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")
|
private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
|
||||||
|
|
||||||
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {
|
class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
|
||||||
|
|
||||||
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
|
||||||
return conditionResolver.resolveRoomMemberCountCondition(this)
|
return conditionResolver.resolveRoomMemberCountCondition(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun technicalDescription(): String {
|
override fun technicalDescription(): String {
|
||||||
return "Room member count is $`is`"
|
return "Room member count is $iz"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
fun isSatisfied(event: Event, session: RoomService?): Boolean {
|
||||||
@ -56,12 +55,9 @@ class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_co
|
|||||||
*/
|
*/
|
||||||
private fun parseIsField(): Pair<String?, Int>? {
|
private fun parseIsField(): Pair<String?, Int>? {
|
||||||
try {
|
try {
|
||||||
val match = regex.matcher(`is`)
|
val match = regex.find(iz) ?: return null
|
||||||
if (match.find()) {
|
val (prefix, count) = match.destructured
|
||||||
val prefix = match.group(1)
|
return prefix to count.toInt()
|
||||||
val count = match.group(2).toInt()
|
|
||||||
return prefix to count
|
|
||||||
}
|
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Timber.d(t)
|
Timber.d(t)
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData
|
|||||||
|
|
||||||
interface InitialSyncProgressService {
|
interface InitialSyncProgressService {
|
||||||
|
|
||||||
fun getLiveStatus() : LiveData<Status?>
|
fun getInitialSyncProgressStatus() : LiveData<Status?>
|
||||||
|
|
||||||
data class Status(
|
data class Status(
|
||||||
@StringRes val statusText: Int?,
|
@StringRes val statusText: Int,
|
||||||
val percentProgress: Int = 0
|
val percentProgress: Int = 0
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.pushers
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
interface PushersService {
|
interface PushersService {
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
|
||||||
data class ReadReceipt(
|
data class ReadReceipt(
|
||||||
val userId: String,
|
val user: User,
|
||||||
val eventId: String,
|
|
||||||
val originServerTs: Long
|
val originServerTs: Long
|
||||||
)
|
)
|
@ -29,7 +29,6 @@ import im.vector.matrix.android.api.session.room.model.PowerLevels
|
|||||||
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameter to create a room, with facilities functions to configure it
|
* Parameter to create a room, with facilities functions to configure it
|
||||||
@ -133,7 +132,7 @@ class CreateRoomParams {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (null == initialStates) {
|
if (null == initialStates) {
|
||||||
initialStates = Arrays.asList<Event>(algoEvent)
|
initialStates = mutableListOf(algoEvent)
|
||||||
} else {
|
} else {
|
||||||
initialStates!!.add(algoEvent)
|
initialStates!!.add(algoEvent)
|
||||||
}
|
}
|
||||||
@ -166,7 +165,7 @@ class CreateRoomParams {
|
|||||||
content = contentMap.toContent())
|
content = contentMap.toContent())
|
||||||
|
|
||||||
if (null == initialStates) {
|
if (null == initialStates) {
|
||||||
initialStates = Arrays.asList<Event>(historyVisibilityEvent)
|
initialStates = mutableListOf(historyVisibilityEvent)
|
||||||
} else {
|
} else {
|
||||||
initialStates!!.add(historyVisibilityEvent)
|
initialStates!!.add(historyVisibilityEvent)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.read
|
package im.vector.matrix.android.api.session.room.read
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
* This interface defines methods to handle read receipts and read marker in a room. It's implemented at the room level.
|
||||||
@ -39,4 +41,6 @@ interface ReadService {
|
|||||||
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
|
||||||
|
|
||||||
fun isEventRead(eventId: String): Boolean
|
fun isEventRead(eventId: String): Boolean
|
||||||
|
|
||||||
|
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||||
}
|
}
|
@ -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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.isReply
|
import im.vector.matrix.android.api.session.room.model.message.isReply
|
||||||
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
|
||||||
@ -37,7 +38,8 @@ data class TimelineEvent(
|
|||||||
val senderName: String?,
|
val senderName: String?,
|
||||||
val isUniqueDisplayName: Boolean,
|
val isUniqueDisplayName: Boolean,
|
||||||
val senderAvatar: String?,
|
val senderAvatar: String?,
|
||||||
val annotations: EventAnnotationsSummary? = null
|
val annotations: EventAnnotationsSummary? = null,
|
||||||
|
val readReceipts: List<ReadReceipt> = emptyList()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val metadata = HashMap<String, Any>()
|
val metadata = HashMap<String, Any>()
|
||||||
|
@ -25,12 +25,12 @@ interface TimelineService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
|
||||||
* You can 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 eventId the optional initial eventId.
|
||||||
* @param allowedTypes the optional filter types
|
* @param settings settings to configure the timeline.
|
||||||
* @return the instantiated 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?
|
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
|
||||||
|
|
||||||
|
)
|
@ -27,7 +27,7 @@ import java.math.BigInteger
|
|||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
import javax.crypto.*
|
import javax.crypto.*
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
@ -479,12 +479,7 @@ object SecretStoringUtils {
|
|||||||
val output = Cipher.getInstance(RSA_MODE)
|
val output = Cipher.getInstance(RSA_MODE)
|
||||||
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.privateKey)
|
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.privateKey)
|
||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
return CipherInputStream(encrypted, output).use { it.readBytes() }
|
||||||
CipherInputStream(encrypted, output).use {
|
|
||||||
it.copyTo(bos)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bos.toByteArray()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
|
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
|
||||||
@ -495,14 +490,7 @@ object SecretStoringUtils {
|
|||||||
val iv = ByteArray(ivSize)
|
val iv = ByteArray(ivSize)
|
||||||
bis.read(iv, 0, ivSize)
|
bis.read(iv, 0, ivSize)
|
||||||
|
|
||||||
|
val encrypted = bis.readBytes()
|
||||||
val bos = ByteArrayOutputStream()
|
|
||||||
var next = bis.read()
|
|
||||||
while (next != -1) {
|
|
||||||
bos.write(next)
|
|
||||||
next = bis.read()
|
|
||||||
}
|
|
||||||
val encrypted = bos.toByteArray()
|
|
||||||
return Pair(iv, encrypted)
|
return Pair(iv, encrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,14 +518,7 @@ object SecretStoringUtils {
|
|||||||
val iv = ByteArray(ivSize)
|
val iv = ByteArray(ivSize)
|
||||||
bis.read(iv)
|
bis.read(iv)
|
||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
val encrypted = bis.readBytes()
|
||||||
|
|
||||||
var next = bis.read()
|
|
||||||
while (next != -1) {
|
|
||||||
bos.write(next)
|
|
||||||
next = bis.read()
|
|
||||||
}
|
|
||||||
val encrypted = bos.toByteArray()
|
|
||||||
return Triple(encryptedKey, iv, encrypted)
|
return Triple(encryptedKey, iv, encrypted)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,14 +560,7 @@ object SecretStoringUtils {
|
|||||||
val iv = ByteArray(ivSize)
|
val iv = ByteArray(ivSize)
|
||||||
bis.read(iv)
|
bis.read(iv)
|
||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
val encrypted = bis.readBytes()
|
||||||
|
|
||||||
var next = bis.read()
|
|
||||||
while (next != -1) {
|
|
||||||
bos.write(next)
|
|
||||||
next = bis.read()
|
|
||||||
}
|
|
||||||
val encrypted = bos.toByteArray()
|
|
||||||
return Triple(salt, iv, encrypted)
|
return Triple(salt, iv, encrypted)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -105,7 +105,7 @@ internal abstract class CryptoModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService
|
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask
|
||||||
|
@ -93,7 +93,7 @@ import kotlin.math.max
|
|||||||
* Specially, it tracks all room membership changes events in order to do keys updates.
|
* Specially, it tracks all room membership changes events in order to do keys updates.
|
||||||
*/
|
*/
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class CryptoManager @Inject constructor(
|
internal class DefaultCryptoService @Inject constructor(
|
||||||
// Olm Manager
|
// Olm Manager
|
||||||
private val olmManager: OlmManager,
|
private val olmManager: OlmManager,
|
||||||
// The credentials,
|
// The credentials,
|
||||||
@ -1067,6 +1067,6 @@ internal class CryptoManager @Inject constructor(
|
|||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
|
return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,7 +17,6 @@
|
|||||||
package im.vector.matrix.android.internal.crypto
|
package im.vector.matrix.android.internal.crypto
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
|
internal class ObjectSigner @Inject constructor(private val credentials: Credentials,
|
||||||
|
@ -31,7 +31,6 @@ import im.vector.matrix.android.internal.crypto.model.event.OlmPayloadContent
|
|||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.matrix.android.internal.util.convertFromUTF8
|
import im.vector.matrix.android.internal.util.convertFromUTF8
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal class MXOlmDecryption(
|
internal class MXOlmDecryption(
|
||||||
// The olm device interface
|
// The olm device interface
|
||||||
@ -158,33 +157,14 @@ internal class MXOlmDecryption(
|
|||||||
* @return payload, if decrypted successfully.
|
* @return payload, if decrypted successfully.
|
||||||
*/
|
*/
|
||||||
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
|
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
|
||||||
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey)
|
val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
|
||||||
|
|
||||||
val sessionIds: List<String>
|
val messageBody = message["body"] as? String ?: return null
|
||||||
|
val messageType = when (val typeAsVoid = message["type"]) {
|
||||||
if (null == sessionIdsSet) {
|
is Double -> typeAsVoid.toInt()
|
||||||
sessionIds = ArrayList()
|
is Int -> typeAsVoid
|
||||||
} else {
|
is Long -> typeAsVoid.toInt()
|
||||||
sessionIds = ArrayList(sessionIdsSet)
|
else -> return null
|
||||||
}
|
|
||||||
|
|
||||||
val messageBody = message["body"] as? String
|
|
||||||
var messageType: Int? = null
|
|
||||||
|
|
||||||
val typeAsVoid = message["type"]
|
|
||||||
|
|
||||||
if (null != typeAsVoid) {
|
|
||||||
if (typeAsVoid is Double) {
|
|
||||||
messageType = typeAsVoid.toInt()
|
|
||||||
} else if (typeAsVoid is Int) {
|
|
||||||
messageType = typeAsVoid
|
|
||||||
} else if (typeAsVoid is Long) {
|
|
||||||
messageType = typeAsVoid.toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null == messageBody || null == messageType) {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try each session in turn
|
// Try each session in turn
|
||||||
|
@ -26,7 +26,6 @@ import java.io.ByteArrayOutputStream
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
@ -59,8 +58,7 @@ object MXEncryptedAttachments {
|
|||||||
// Half of the IV is random, the lower order bits are zeroed
|
// Half of the IV is random, the lower order bits are zeroed
|
||||||
// such that the counter never wraps.
|
// such that the counter never wraps.
|
||||||
// See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
|
// See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
|
||||||
val initVectorBytes = ByteArray(16)
|
val initVectorBytes = ByteArray(16) { 0.toByte() }
|
||||||
Arrays.fill(initVectorBytes, 0.toByte())
|
|
||||||
|
|
||||||
val ivRandomPart = ByteArray(8)
|
val ivRandomPart = ByteArray(8)
|
||||||
secureRandom.nextBytes(ivRandomPart)
|
secureRandom.nextBytes(ivRandomPart)
|
||||||
@ -115,7 +113,7 @@ object MXEncryptedAttachments {
|
|||||||
encryptedByteArray = outStream.toByteArray()
|
encryptedByteArray = outStream.toByteArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms")
|
Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms")
|
||||||
return Try.just(result)
|
return Try.just(result)
|
||||||
} catch (oom: OutOfMemoryError) {
|
} catch (oom: OutOfMemoryError) {
|
||||||
Timber.e(oom, "## encryptAttachment failed")
|
Timber.e(oom, "## encryptAttachment failed")
|
||||||
@ -206,13 +204,13 @@ object MXEncryptedAttachments {
|
|||||||
val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
|
val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
|
||||||
outStream.close()
|
outStream.close()
|
||||||
|
|
||||||
Timber.v("Decrypt in " + (System.currentTimeMillis() - t0) + " ms")
|
Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms")
|
||||||
|
|
||||||
return decryptedStream
|
return decryptedStream
|
||||||
} catch (oom: OutOfMemoryError) {
|
} catch (oom: OutOfMemoryError) {
|
||||||
Timber.e(oom, "## decryptAttachment() : failed " + oom.message)
|
Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "## decryptAttachment() : failed " + e.message)
|
Timber.e(e, "## decryptAttachment() : failed ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -228,34 +226,20 @@ object MXEncryptedAttachments {
|
|||||||
* Base64 URL conversion methods
|
* Base64 URL conversion methods
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private fun base64UrlToBase64(base64Url: String?): String? {
|
private fun base64UrlToBase64(base64Url: String): String {
|
||||||
var result = base64Url
|
return base64Url.replace('-', '+')
|
||||||
if (null != result) {
|
.replace('_', '/')
|
||||||
result = result.replace("-".toRegex(), "+")
|
|
||||||
result = result.replace("_".toRegex(), "/")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
private fun base64ToBase64Url(base64: String): String {
|
||||||
|
return base64.replace("\n".toRegex(), "")
|
||||||
|
.replace("\\+".toRegex(), "-")
|
||||||
|
.replace('/', '_')
|
||||||
|
.replace("=", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun base64ToBase64Url(base64: String?): String? {
|
private fun base64ToUnpaddedBase64(base64: String): String {
|
||||||
var result = base64
|
return base64.replace("\n".toRegex(), "")
|
||||||
if (null != result) {
|
.replace("=", "")
|
||||||
result = result.replace("\n".toRegex(), "")
|
|
||||||
result = result.replace("\\+".toRegex(), "-")
|
|
||||||
result = result.replace("/".toRegex(), "_")
|
|
||||||
result = result.replace("=".toRegex(), "")
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun base64ToUnpaddedBase64(base64: String?): String? {
|
|
||||||
var result = base64
|
|
||||||
if (null != result) {
|
|
||||||
result = result.replace("\n".toRegex(), "")
|
|
||||||
result = result.replace("=".toRegex(), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,9 +66,8 @@ import org.matrix.olm.OlmPkEncryption
|
|||||||
import org.matrix.olm.OlmPkMessage
|
import org.matrix.olm.OlmPkMessage
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.HashMap
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A KeysBackup class instance manage incremental backup of e2e keys (megolm keys)
|
* A KeysBackup class instance manage incremental backup of e2e keys (megolm keys)
|
||||||
@ -114,8 +113,6 @@ internal class KeysBackup @Inject constructor(
|
|||||||
// The backup key being used.
|
// The backup key being used.
|
||||||
private var backupOlmPkEncryption: OlmPkEncryption? = null
|
private var backupOlmPkEncryption: OlmPkEncryption? = null
|
||||||
|
|
||||||
private val random = Random()
|
|
||||||
|
|
||||||
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null
|
||||||
|
|
||||||
private var keysBackupStateListener: KeysBackupStateListener? = null
|
private var keysBackupStateListener: KeysBackupStateListener? = null
|
||||||
@ -848,7 +845,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
// Wait between 0 and 10 seconds, to avoid backup requests from
|
// Wait between 0 and 10 seconds, to avoid backup requests from
|
||||||
// different clients hitting the server all at the same time when a
|
// different clients hitting the server all at the same time when a
|
||||||
// new key is sent
|
// new key is sent
|
||||||
val delayInMs = random.nextInt(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS).toLong()
|
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
|
||||||
|
|
||||||
uiHandler.postDelayed({ backupKeys() }, delayInMs)
|
uiHandler.postDelayed({ backupKeys() }, delayInMs)
|
||||||
}
|
}
|
||||||
@ -1307,7 +1304,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
// Make the request
|
// Make the request
|
||||||
storeSessionDataTask
|
storeSessionDataTask
|
||||||
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)){
|
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) {
|
||||||
this.callback = sendingRequestCallback
|
this.callback = sendingRequestCallback
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
@ -1405,7 +1402,7 @@ internal class KeysBackup @Inject constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Maximum delay in ms in {@link maybeBackupKeys}
|
// Maximum delay in ms in {@link maybeBackupKeys}
|
||||||
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10000
|
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
|
||||||
|
|
||||||
// Maximum number of keys to send at a time to the homeserver.
|
// Maximum number of keys to send at a time to the homeserver.
|
||||||
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100
|
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100
|
||||||
|
@ -22,7 +22,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup
|
|||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import kotlin.experimental.xor
|
import kotlin.experimental.xor
|
||||||
|
@ -20,7 +20,6 @@ import android.os.Handler
|
|||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal class KeysBackupStateManager(private val uiHandler: Handler) {
|
internal class KeysBackupStateManager(private val uiHandler: Handler) {
|
||||||
|
|
||||||
|
@ -23,7 +23,6 @@ import com.squareup.moshi.JsonClass
|
|||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MXDeviceInfo(
|
data class MXDeviceInfo(
|
||||||
|
@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.model
|
|||||||
|
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class MXKey(
|
data class MXKey(
|
||||||
/**
|
/**
|
||||||
@ -46,11 +45,7 @@ data class MXKey(
|
|||||||
* @return the signed data map
|
* @return the signed data map
|
||||||
*/
|
*/
|
||||||
fun signalableJSONDictionary(): Map<String, Any> {
|
fun signalableJSONDictionary(): Map<String, Any> {
|
||||||
val map = HashMap<String, Any>()
|
return mapOf("key" to value)
|
||||||
|
|
||||||
map["key"] = value
|
|
||||||
|
|
||||||
return map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.crypto.model
|
package im.vector.matrix.android.internal.crypto.model
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class MXUsersDevicesMap<E> {
|
class MXUsersDevicesMap<E> {
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ class MXUsersDevicesMap<E> {
|
|||||||
* @return the user Ids
|
* @return the user Ids
|
||||||
*/
|
*/
|
||||||
val userIds: List<String>
|
val userIds: List<String>
|
||||||
get() = ArrayList(map.keys)
|
get() = map.keys.toList()
|
||||||
|
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
get() = map.isEmpty()
|
get() = map.isEmpty()
|
||||||
@ -40,7 +39,7 @@ class MXUsersDevicesMap<E> {
|
|||||||
* @return the device ids list
|
* @return the device ids list
|
||||||
*/
|
*/
|
||||||
fun getUserDeviceIds(userId: String?): List<String>? {
|
fun getUserDeviceIds(userId: String?): List<String>? {
|
||||||
return if (userId?.isNotBlank() == true && map.containsKey(userId)) {
|
return if (!userId.isNullOrBlank() && map.containsKey(userId)) {
|
||||||
map[userId]!!.keys.toList()
|
map[userId]!!.keys.toList()
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
@ -53,7 +52,7 @@ class MXUsersDevicesMap<E> {
|
|||||||
* @return the object
|
* @return the object
|
||||||
*/
|
*/
|
||||||
fun getObject(userId: String?, deviceId: String?): E? {
|
fun getObject(userId: String?, deviceId: String?): E? {
|
||||||
return if (userId?.isNotBlank() == true && deviceId?.isNotBlank() == true && map.containsKey(userId)) {
|
return if (!userId.isNullOrBlank() && !deviceId.isNullOrBlank()) {
|
||||||
map[userId]?.get(deviceId)
|
map[userId]?.get(deviceId)
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
@ -67,11 +66,8 @@ class MXUsersDevicesMap<E> {
|
|||||||
*/
|
*/
|
||||||
fun setObject(userId: String?, deviceId: String?, o: E?) {
|
fun setObject(userId: String?, deviceId: String?, o: E?) {
|
||||||
if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) {
|
if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) {
|
||||||
if (map[userId] == null) {
|
val devices = map.getOrPut(userId) { HashMap() }
|
||||||
map[userId] = HashMap()
|
devices[deviceId] = o
|
||||||
}
|
|
||||||
|
|
||||||
map[userId]?.put(deviceId, o)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +78,7 @@ class MXUsersDevicesMap<E> {
|
|||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
*/
|
*/
|
||||||
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
|
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
|
||||||
if (userId?.isNotBlank() == true) {
|
if (!userId.isNullOrBlank()) {
|
||||||
if (null == objectsPerDevices) {
|
if (null == objectsPerDevices) {
|
||||||
map.remove(userId)
|
map.remove(userId)
|
||||||
} else {
|
} else {
|
||||||
@ -97,7 +93,7 @@ class MXUsersDevicesMap<E> {
|
|||||||
* @param userId the user id.
|
* @param userId the user id.
|
||||||
*/
|
*/
|
||||||
fun removeUserObjects(userId: String?) {
|
fun removeUserObjects(userId: String?) {
|
||||||
if (userId?.isNotBlank() == true) {
|
if (!userId.isNullOrBlank()) {
|
||||||
map.remove(userId)
|
map.remove(userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
|||||||
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
|
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
|
internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
|
||||||
data class Params(
|
data class Params(
|
||||||
@ -45,7 +45,7 @@ internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi
|
|||||||
return executeRequest {
|
return executeRequest {
|
||||||
apiCall = cryptoApi.sendToDevice(
|
apiCall = cryptoApi.sendToDevice(
|
||||||
params.eventType,
|
params.eventType,
|
||||||
params.transactionId ?: Random().nextInt(Integer.MAX_VALUE).toString(),
|
params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
|
||||||
sendToDeviceBody
|
sendToDeviceBody
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
cancelTransaction(
|
cancelTransaction(
|
||||||
startReq.transactionID!!,
|
startReq.transactionID!!,
|
||||||
otherUserId!!,
|
otherUserId!!,
|
||||||
startReq?.fromDevice ?: event.getSenderKey()!!,
|
startReq.fromDevice ?: event.getSenderKey()!!,
|
||||||
CancelCode.UnknownMethod
|
CancelCode.UnknownMethod
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -388,14 +388,13 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
|
|||||||
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
|
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
|
||||||
*/
|
*/
|
||||||
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
|
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
|
||||||
val buff = StringBuffer()
|
return buildString {
|
||||||
buff
|
append(credentials.userId).append("|")
|
||||||
.append(credentials.userId).append("|")
|
append(credentials.deviceId).append("|")
|
||||||
.append(credentials.deviceId).append("|")
|
append(userId).append("|")
|
||||||
.append(userId).append("|")
|
append(deviceID).append("|")
|
||||||
.append(deviceID).append("|")
|
append(UUID.randomUUID().toString())
|
||||||
.append(UUID.randomUUID().toString())
|
}
|
||||||
return buff.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.mapper.toEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
import im.vector.matrix.android.internal.extensions.assertIsManaged
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||||
@ -133,6 +136,28 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val localId = TimelineEventEntity.nextId(realm)
|
val localId = TimelineEventEntity.nextId(realm)
|
||||||
|
val eventId = event.eventId ?: ""
|
||||||
|
val senderId = event.senderId ?: ""
|
||||||
|
|
||||||
|
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
|
||||||
|
?: ReadReceiptsSummaryEntity(eventId, roomId)
|
||||||
|
|
||||||
|
// Update RR for the sender of a new message with a dummy one
|
||||||
|
|
||||||
|
if (event.originServerTs != null) {
|
||||||
|
val timestampOfEvent = event.originServerTs.toDouble()
|
||||||
|
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId)
|
||||||
|
// If the synced RR is older, update
|
||||||
|
if (timestampOfEvent > readReceiptOfSender.originServerTs) {
|
||||||
|
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()
|
||||||
|
readReceiptOfSender.eventId = eventId
|
||||||
|
readReceiptOfSender.originServerTs = timestampOfEvent
|
||||||
|
previousReceiptsSummary?.readReceipts?.remove(readReceiptOfSender)
|
||||||
|
readReceiptsSummaryEntity.readReceipts.add(readReceiptOfSender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val eventEntity = TimelineEventEntity(localId).also {
|
val eventEntity = TimelineEventEntity(localId).also {
|
||||||
it.root = event.toEntity(roomId).apply {
|
it.root = event.toEntity(roomId).apply {
|
||||||
this.stateIndex = currentStateIndex
|
this.stateIndex = currentStateIndex
|
||||||
@ -140,9 +165,10 @@ internal fun ChunkEntity.add(roomId: String,
|
|||||||
this.displayIndex = currentDisplayIndex
|
this.displayIndex = currentDisplayIndex
|
||||||
this.sendState = SendState.SYNCED
|
this.sendState = SendState.SYNCED
|
||||||
}
|
}
|
||||||
it.eventId = event.eventId ?: ""
|
it.eventId = eventId
|
||||||
it.roomId = roomId
|
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
|
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.timelineEvents.size
|
||||||
timelineEvents.add(position, eventEntity)
|
timelineEvents.add(position, eventEntity)
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,11 +22,12 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomSummaryMapper @Inject constructor(
|
internal class RoomSummaryMapper @Inject constructor(
|
||||||
val cryptoService: CryptoService
|
val cryptoService: CryptoService,
|
||||||
|
val timelineEventMapper: TimelineEventMapper
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||||
@ -34,7 +35,9 @@ internal class RoomSummaryMapper @Inject constructor(
|
|||||||
RoomTag(it.tagName, it.tagOrder)
|
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) {
|
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
|
||||||
//TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
//TODO use a global event decryptor? attache to session and that listen to new sessionId?
|
||||||
//for now decrypt sync
|
//for now decrypt sync
|
||||||
|
@ -17,13 +17,23 @@
|
|||||||
package im.vector.matrix.android.internal.database.mapper
|
package im.vector.matrix.android.internal.database.mapper
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
import im.vector.matrix.android.internal.database.model.TimelineEventEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object TimelineEventMapper {
|
internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) {
|
||||||
|
|
||||||
fun map(timelineEventEntity: TimelineEventEntity): TimelineEvent {
|
|
||||||
|
|
||||||
|
fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent {
|
||||||
|
val readReceipts = if (buildReadReceipts) {
|
||||||
|
correctedReadReceipts ?: timelineEventEntity.readReceipts
|
||||||
|
?.let {
|
||||||
|
readReceiptsSummaryMapper.map(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
return TimelineEvent(
|
return TimelineEvent(
|
||||||
root = timelineEventEntity.root?.asDomain()
|
root = timelineEventEntity.root?.asDomain()
|
||||||
?: Event("", timelineEventEntity.eventId),
|
?: Event("", timelineEventEntity.eventId),
|
||||||
@ -32,14 +42,13 @@ internal object TimelineEventMapper {
|
|||||||
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
|
displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
|
||||||
senderName = timelineEventEntity.senderName,
|
senderName = timelineEventEntity.senderName,
|
||||||
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
|
||||||
senderAvatar = timelineEventEntity.senderAvatar
|
senderAvatar = timelineEventEntity.senderAvatar,
|
||||||
|
readReceipts = readReceipts?.sortedByDescending {
|
||||||
|
it.originServerTs
|
||||||
|
} ?: emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun TimelineEventEntity.asDomain(): TimelineEvent {
|
|
||||||
return TimelineEventMapper.map(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,13 +17,18 @@
|
|||||||
package im.vector.matrix.android.internal.database.model
|
package im.vector.matrix.android.internal.database.model
|
||||||
|
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import io.realm.annotations.LinkingObjects
|
||||||
import io.realm.annotations.PrimaryKey
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
internal open class ReadReceiptEntity(@PrimaryKey var primaryKey: String = "",
|
internal open class ReadReceiptEntity(@PrimaryKey var primaryKey: String = "",
|
||||||
var userId: String = "",
|
|
||||||
var eventId: String = "",
|
var eventId: String = "",
|
||||||
var roomId: String = "",
|
var roomId: String = "",
|
||||||
|
var userId: String = "",
|
||||||
var originServerTs: Double = 0.0
|
var originServerTs: Double = 0.0
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
companion object
|
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
|
||||||
|
|
||||||
|
}
|
@ -42,6 +42,7 @@ import io.realm.annotations.RealmModule
|
|||||||
PushRuleEntity::class,
|
PushRuleEntity::class,
|
||||||
PushConditionEntity::class,
|
PushConditionEntity::class,
|
||||||
PusherEntity::class,
|
PusherEntity::class,
|
||||||
PusherDataEntity::class
|
PusherDataEntity::class,
|
||||||
|
ReadReceiptsSummaryEntity::class
|
||||||
])
|
])
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
@ -30,7 +30,8 @@ internal open class TimelineEventEntity(var localId: Long = 0,
|
|||||||
var senderName: String? = null,
|
var senderName: String? = null,
|
||||||
var isUniqueDisplayName: Boolean = false,
|
var isUniqueDisplayName: Boolean = false,
|
||||||
var senderAvatar: String? = null,
|
var senderAvatar: String? = null,
|
||||||
var senderMembershipEvent: EventEntity? = null
|
var senderMembershipEvent: EventEntity? = null,
|
||||||
|
var readReceipts: ReadReceiptsSummaryEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
@LinkingObjects("timelineEvents")
|
@LinkingObjects("timelineEvents")
|
||||||
|
@ -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"*}"""
|
||||||
|
|
||||||
|
}
|
@ -27,3 +27,21 @@ internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, use
|
|||||||
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
|
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
|
||||||
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
|
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
|
||||||
|
return ReadReceiptEntity().apply {
|
||||||
|
this.primaryKey = "${roomId}_$userId"
|
||||||
|
this.eventId = eventId
|
||||||
|
this.roomId = roomId
|
||||||
|
this.userId = userId
|
||||||
|
this.originServerTs = originServerTs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
|
||||||
|
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
|
||||||
|
?: realm.createObject(ReadReceiptEntity::class.java, "${roomId}_$userId").apply {
|
||||||
|
this.roomId = roomId
|
||||||
|
this.userId = userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
@ -24,7 +24,6 @@ import java.security.KeyStore
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.cert.CertificateException
|
import java.security.cert.CertificateException
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.*
|
|
||||||
import javax.net.ssl.*
|
import javax.net.ssl.*
|
||||||
import kotlin.experimental.and
|
import kotlin.experimental.and
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ import java.net.UnknownHostException
|
|||||||
import java.security.KeyManagementException
|
import java.security.KeyManagementException
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.SSLSocket
|
import javax.net.ssl.SSLSocket
|
||||||
import javax.net.ssl.SSLSocketFactory
|
import javax.net.ssl.SSLSocketFactory
|
||||||
@ -101,25 +100,16 @@ constructor(trustPinned: Array<TrustManager>, acceptedTlsVersions: List<TlsVersi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun enableTLSOnSocket(socket: Socket?): Socket? {
|
private fun enableTLSOnSocket(socket: Socket?): Socket? {
|
||||||
if (socket != null && socket is SSLSocket) {
|
if (socket is SSLSocket) {
|
||||||
val sslSocket = socket as SSLSocket?
|
val supportedProtocols = socket.supportedProtocols.toSet()
|
||||||
|
val filteredEnabledProtocols = enabledProtocols.filter { it in supportedProtocols }
|
||||||
|
|
||||||
val supportedProtocols = Arrays.asList(*sslSocket!!.supportedProtocols)
|
if (filteredEnabledProtocols.isNotEmpty()) {
|
||||||
val filteredEnabledProtocols = ArrayList<String>()
|
|
||||||
|
|
||||||
for (protocol in enabledProtocols) {
|
|
||||||
if (supportedProtocols.contains(protocol)) {
|
|
||||||
filteredEnabledProtocols.add(protocol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filteredEnabledProtocols.isEmpty()) {
|
|
||||||
try {
|
try {
|
||||||
sslSocket.enabledProtocols = filteredEnabledProtocols.toTypedArray()
|
socket.enabledProtocols = filteredEnabledProtocols.toTypedArray()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return socket
|
return socket
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.matrix.android.internal.session
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
||||||
@ -25,31 +26,33 @@ import javax.inject.Inject
|
|||||||
@SessionScope
|
@SessionScope
|
||||||
class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgressService {
|
class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgressService {
|
||||||
|
|
||||||
var status = MutableLiveData<InitialSyncProgressService.Status>()
|
private var status = MutableLiveData<InitialSyncProgressService.Status>()
|
||||||
|
|
||||||
var rootTask: TaskInfo? = null
|
private var rootTask: TaskInfo? = null
|
||||||
|
|
||||||
override fun getLiveStatus(): LiveData<InitialSyncProgressService.Status?> {
|
override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status?> {
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startTask(@StringRes nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
|
||||||
fun startTask(nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
|
// Create a rootTask, or add a child to the leaf
|
||||||
if (rootTask == null) {
|
if (rootTask == null) {
|
||||||
rootTask = TaskInfo(nameRes, totalProgress)
|
rootTask = TaskInfo(nameRes, totalProgress)
|
||||||
} else {
|
} else {
|
||||||
val currentLeaf = rootTask!!.leaf()
|
val currentLeaf = rootTask!!.leaf()
|
||||||
val newTask = TaskInfo(nameRes, totalProgress)
|
|
||||||
newTask.parent = currentLeaf
|
val newTask = TaskInfo(nameRes,
|
||||||
newTask.offset = currentLeaf.currentProgress
|
totalProgress,
|
||||||
|
currentLeaf,
|
||||||
|
parentWeight)
|
||||||
|
|
||||||
currentLeaf.child = newTask
|
currentLeaf.child = newTask
|
||||||
newTask.parentWeight = parentWeight
|
|
||||||
}
|
}
|
||||||
reportProgress(0)
|
reportProgress(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportProgress(progress: Int) {
|
fun reportProgress(progress: Int) {
|
||||||
rootTask?.leaf()?.incrementProgress(progress)
|
rootTask?.leaf()?.setProgress(progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endTask(nameRes: Int) {
|
fun endTask(nameRes: Int) {
|
||||||
@ -58,7 +61,7 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
|
|||||||
//close it
|
//close it
|
||||||
val parent = endedTask.parent
|
val parent = endedTask.parent
|
||||||
parent?.child = null
|
parent?.child = null
|
||||||
parent?.incrementProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt())
|
parent?.setProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt())
|
||||||
}
|
}
|
||||||
if (endedTask?.parent == null) {
|
if (endedTask?.parent == null) {
|
||||||
status.postValue(null)
|
status.postValue(null)
|
||||||
@ -71,14 +74,17 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inner class TaskInfo(var nameRes: Int,
|
private inner class TaskInfo(@StringRes var nameRes: Int,
|
||||||
var totalProgress: Int) {
|
var totalProgress: Int,
|
||||||
var parent: TaskInfo? = null
|
var parent: TaskInfo? = null,
|
||||||
|
var parentWeight: Float = 1f,
|
||||||
|
var offset: Int = parent?.currentProgress ?: 0) {
|
||||||
var child: TaskInfo? = null
|
var child: TaskInfo? = null
|
||||||
var parentWeight: Float = 1f
|
|
||||||
var currentProgress: Int = 0
|
var currentProgress: Int = 0
|
||||||
var offset: Int = 0
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the further child
|
||||||
|
*/
|
||||||
fun leaf(): TaskInfo {
|
fun leaf(): TaskInfo {
|
||||||
var last = this
|
var last = this
|
||||||
while (last.child != null) {
|
while (last.child != null) {
|
||||||
@ -87,26 +93,27 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
|
|||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
fun incrementProgress(progress: Int) {
|
/**
|
||||||
|
* Set progress of the parent if any (which will post value), or post the value
|
||||||
|
*/
|
||||||
|
fun setProgress(progress: Int) {
|
||||||
currentProgress = progress
|
currentProgress = progress
|
||||||
// val newProgress = Math.min(currentProgress + progress, totalProgress)
|
// val newProgress = Math.min(currentProgress + progress, totalProgress)
|
||||||
parent?.let {
|
parent?.let {
|
||||||
val parentProgress = (currentProgress * parentWeight).toInt()
|
val parentProgress = (currentProgress * parentWeight).toInt()
|
||||||
it.incrementProgress(offset + parentProgress)
|
it.setProgress(offset + parentProgress)
|
||||||
}
|
} ?: run {
|
||||||
if (parent == null) {
|
Timber.e("--- ${leaf().nameRes}: $currentProgress")
|
||||||
Timber.e("--- ${leaf().nameRes}: ${currentProgress}")
|
|
||||||
status.postValue(
|
status.postValue(
|
||||||
InitialSyncProgressService.Status(leaf().nameRes, currentProgress)
|
InitialSyncProgressService.Status(leaf().nameRes, currentProgress)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <T> reportSubtask(reporter: DefaultInitialSyncProgressService?,
|
inline fun <T> reportSubtask(reporter: DefaultInitialSyncProgressService?,
|
||||||
nameRes: Int,
|
@StringRes nameRes: Int,
|
||||||
totalProgress: Int,
|
totalProgress: Int,
|
||||||
parentWeight: Float = 1f,
|
parentWeight: Float = 1f,
|
||||||
block: () -> T): T {
|
block: () -> T): T {
|
||||||
@ -121,11 +128,11 @@ inline fun <K, V, R> Map<out K, V>.mapWithProgress(reporter: DefaultInitialSyncP
|
|||||||
taskId: Int,
|
taskId: Int,
|
||||||
weight: Float,
|
weight: Float,
|
||||||
transform: (Map.Entry<K, V>) -> R): List<R> {
|
transform: (Map.Entry<K, V>) -> R): List<R> {
|
||||||
val total = count()
|
val total = count().toFloat()
|
||||||
var current = 0
|
var current = 0
|
||||||
reporter?.startTask(taskId, 100, weight)
|
reporter?.startTask(taskId, 100, weight)
|
||||||
return this.map {
|
return map {
|
||||||
reporter?.reportProgress((current / total.toFloat() * 100).toInt())
|
reporter?.reportProgress((current / total * 100).toInt())
|
||||||
current++
|
current++
|
||||||
transform.invoke(it)
|
transform.invoke(it)
|
||||||
}.also {
|
}.also {
|
||||||
|
@ -40,7 +40,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
|
|||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
import im.vector.matrix.android.api.util.MatrixCallbackDelegate
|
import im.vector.matrix.android.api.util.MatrixCallbackDelegate
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
@ -63,7 +63,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
|||||||
private val signOutService: Lazy<SignOutService>,
|
private val signOutService: Lazy<SignOutService>,
|
||||||
private val pushRuleService: Lazy<PushRuleService>,
|
private val pushRuleService: Lazy<PushRuleService>,
|
||||||
private val pushersService: Lazy<PushersService>,
|
private val pushersService: Lazy<PushersService>,
|
||||||
private val cryptoService: Lazy<CryptoManager>,
|
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||||
private val fileService: Lazy<FileService>,
|
private val fileService: Lazy<FileService>,
|
||||||
private val syncThreadProvider: Provider<SyncThread>,
|
private val syncThreadProvider: Provider<SyncThread>,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
|
@ -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.api.session.Session
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoModule
|
import im.vector.matrix.android.internal.crypto.CryptoModule
|
||||||
import im.vector.matrix.android.internal.di.MatrixComponent
|
import im.vector.matrix.android.internal.di.MatrixComponent
|
||||||
|
import im.vector.matrix.android.internal.di.SessionAssistedInjectModule
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
import im.vector.matrix.android.internal.session.cache.CacheModule
|
import im.vector.matrix.android.internal.session.cache.CacheModule
|
||||||
import im.vector.matrix.android.internal.session.content.ContentModule
|
import im.vector.matrix.android.internal.session.content.ContentModule
|
||||||
@ -59,7 +60,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
|||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
PushersModule::class,
|
PushersModule::class,
|
||||||
AccountDataModule::class
|
AccountDataModule::class,
|
||||||
|
SessionAssistedInjectModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
@ -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.AccessTokenInterceptor
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||||
|
import im.vector.matrix.android.internal.session.room.DefaultRoomFactory
|
||||||
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
||||||
|
import im.vector.matrix.android.internal.session.room.RoomFactory
|
||||||
import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver
|
import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLiveObserver
|
||||||
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
|
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
|
||||||
import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver
|
import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver
|
||||||
@ -114,7 +116,6 @@ internal abstract class SessionModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSession(session: DefaultSession): Session
|
abstract fun bindSession(session: DefaultSession): Session
|
||||||
|
|
||||||
|
@ -30,9 +30,8 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
|||||||
private val listeners = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>()
|
private val listeners = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>()
|
||||||
|
|
||||||
override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
|
override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
|
||||||
val listeners = listeners[key] ?: ArrayList()
|
val listeners = listeners.getOrPut(key) { ArrayList() }
|
||||||
listeners.add(updateListener)
|
listeners.add(updateListener)
|
||||||
this.listeners[key] = listeners
|
|
||||||
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
|
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
|
||||||
mainHandler.post { updateListener.onUpdate(currentState) }
|
mainHandler.post { updateListener.onUpdate(currentState) }
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ import im.vector.matrix.android.internal.task.configureWith
|
|||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil
|
||||||
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -16,70 +16,46 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room
|
package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
|
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
|
||||||
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
|
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
|
||||||
import im.vector.matrix.android.internal.session.room.relation.FetchEditHistoryTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
||||||
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
|
||||||
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
||||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
|
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomFactory @Inject constructor(private val context: Context,
|
|
||||||
private val credentials: Credentials,
|
internal interface RoomFactory {
|
||||||
private val monarchy: Monarchy,
|
fun create(roomId: String): Room
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
}
|
||||||
|
|
||||||
|
internal class DefaultRoomFactory @Inject constructor(private val monarchy: Monarchy,
|
||||||
private val roomSummaryMapper: RoomSummaryMapper,
|
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 cryptoService: CryptoService,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val timelineServiceFactory: DefaultTimelineService.Factory,
|
||||||
private val fetchEditHistoryTask: FetchEditHistoryTask,
|
private val sendServiceFactory: DefaultSendService.Factory,
|
||||||
private val joinRoomTask: JoinRoomTask,
|
private val stateServiceFactory: DefaultStateService.Factory,
|
||||||
private val leaveRoomTask: LeaveRoomTask) {
|
private val readServiceFactory: DefaultReadService.Factory,
|
||||||
|
private val relationServiceFactory: DefaultRelationService.Factory,
|
||||||
fun create(roomId: String): Room {
|
private val membershipServiceFactory: DefaultMembershipService.Factory) :
|
||||||
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask)
|
RoomFactory {
|
||||||
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)
|
|
||||||
|
|
||||||
|
override fun create(roomId: String): Room {
|
||||||
return DefaultRoom(
|
return DefaultRoom(
|
||||||
roomId,
|
roomId,
|
||||||
monarchy,
|
monarchy,
|
||||||
roomSummaryMapper,
|
roomSummaryMapper,
|
||||||
timelineService,
|
timelineServiceFactory.create(roomId),
|
||||||
sendService,
|
sendServiceFactory.create(roomId),
|
||||||
stateService,
|
stateServiceFactory.create(roomId),
|
||||||
readService,
|
readServiceFactory.create(roomId),
|
||||||
cryptoService,
|
cryptoService,
|
||||||
relationService,
|
relationServiceFactory.create(roomId),
|
||||||
roomMembersService
|
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.file.FileService
|
||||||
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
|
||||||
import im.vector.matrix.android.api.session.room.state.StateService
|
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
|
||||||
import im.vector.matrix.android.internal.session.DefaultFileService
|
import im.vector.matrix.android.internal.session.DefaultFileService
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
@ -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.GetPublicRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
|
import im.vector.matrix.android.internal.session.room.directory.GetThirdPartyProtocolsTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.DefaultLoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
|
||||||
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultInviteTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.DefaultJoinRoomTask
|
||||||
@ -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.membership.leaving.LeaveRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask
|
import im.vector.matrix.android.internal.session.room.prune.DefaultPruneEventTask
|
||||||
import im.vector.matrix.android.internal.session.room.prune.PruneEventTask
|
import im.vector.matrix.android.internal.session.room.prune.PruneEventTask
|
||||||
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
|
|
||||||
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
|
import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask
|
||||||
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
|
||||||
import im.vector.matrix.android.internal.session.room.relation.*
|
import im.vector.matrix.android.internal.session.room.relation.DefaultFetchEditHistoryTask
|
||||||
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
|
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.DefaultSendStateTask
|
||||||
import im.vector.matrix.android.internal.session.room.state.DefaultStateService
|
|
||||||
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
import im.vector.matrix.android.internal.session.room.state.SendStateTask
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.*
|
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
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@ -71,6 +69,9 @@ internal abstract class RoomModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindRoomFactory(roomFactory: DefaultRoomFactory): RoomFactory
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindRoomService(roomService: DefaultRoomService): RoomService
|
abstract fun bindRoomService(roomService: DefaultRoomService): RoomService
|
||||||
|
|
||||||
@ -98,24 +99,15 @@ internal abstract class RoomModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask
|
abstract fun bindLeaveRoomTask(leaveRoomTask: DefaultLeaveRoomTask): LeaveRoomTask
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindMembershipService(membershipService: DefaultMembershipService): MembershipService
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask
|
abstract fun bindLoadRoomMembersTask(loadRoomMembersTask: DefaultLoadRoomMembersTask): LoadRoomMembersTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask
|
abstract fun bindPruneEventTask(pruneEventTask: DefaultPruneEventTask): PruneEventTask
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindReadService(readService: DefaultReadService): ReadService
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask
|
abstract fun bindSetReadMarkersTask(setReadMarkersTask: DefaultSetReadMarkersTask): SetReadMarkersTask
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindRelationService(relationService: DefaultRelationService): RelationService
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
|
abstract fun bindFindReactionEventForUndoTask(findReactionEventForUndoTask: DefaultFindReactionEventForUndoTask): FindReactionEventForUndoTask
|
||||||
|
|
||||||
@ -125,21 +117,12 @@ internal abstract class RoomModule {
|
|||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask
|
abstract fun bindSendStateTask(sendStateTask: DefaultSendStateTask): SendStateTask
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindSendService(sendService: DefaultSendService): SendService
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindStateService(stateService: DefaultStateService): StateService
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
|
abstract fun bindGetContextOfEventTask(getContextOfEventTask: DefaultGetContextOfEventTask): GetContextOfEventTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
|
abstract fun bindPaginationTask(paginationTask: DefaultPaginationTask): PaginationTask
|
||||||
|
|
||||||
@Binds
|
|
||||||
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
abstract fun bindFileService(fileService: DefaultFileService): FileService
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.membership
|
package im.vector.matrix.android.internal.session.room.membership
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
@ -31,9 +33,8 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRo
|
|||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class DefaultMembershipService @Inject constructor(private val roomId: String,
|
internal class DefaultMembershipService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
@ -42,6 +43,11 @@ internal class DefaultMembershipService @Inject constructor(private val roomId:
|
|||||||
private val leaveRoomTask: LeaveRoomTask
|
private val leaveRoomTask: LeaveRoomTask
|
||||||
) : MembershipService {
|
) : MembershipService {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): MembershipService
|
||||||
|
}
|
||||||
|
|
||||||
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
|
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||||
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
|
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
|
||||||
return loadRoomMembersTask
|
return loadRoomMembersTask
|
||||||
|
@ -16,24 +16,38 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.read
|
package im.vector.matrix.android.internal.session.room.read
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||||
|
import im.vector.matrix.android.internal.database.RealmLiveData
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.query.find
|
import im.vector.matrix.android.internal.database.query.find
|
||||||
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class DefaultReadService @Inject constructor(private val roomId: String,
|
internal class DefaultReadService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val setReadMarkersTask: SetReadMarkersTask,
|
private val setReadMarkersTask: SetReadMarkersTask,
|
||||||
private val credentials: Credentials) : ReadService {
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper,
|
||||||
|
private val credentials: Credentials
|
||||||
|
) : ReadService {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): ReadService
|
||||||
|
}
|
||||||
|
|
||||||
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
|
||||||
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
|
val params = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
|
||||||
@ -79,4 +93,16 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
|
|||||||
return isEventRead
|
return isEventRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
|
||||||
|
val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
|
||||||
|
ReadReceiptsSummaryEntity.where(realm, eventId)
|
||||||
|
}
|
||||||
|
return Transformations.map(liveEntity) { realmResults ->
|
||||||
|
realmResults.firstOrNull()?.let {
|
||||||
|
readReceiptsSummaryMapper.map(it)
|
||||||
|
}?.sortedByDescending {
|
||||||
|
it.originServerTs
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -19,6 +19,8 @@ import android.content.Context
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
@ -45,11 +47,10 @@ import im.vector.matrix.android.internal.task.configureWith
|
|||||||
import im.vector.matrix.android.internal.util.CancelableWork
|
import im.vector.matrix.android.internal.util.CancelableWork
|
||||||
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class DefaultRelationService @Inject constructor(private val context: Context,
|
internal class DefaultRelationService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
|
private val context: Context,
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
private val roomId: String,
|
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
@ -58,20 +59,21 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
private val taskExecutor: TaskExecutor)
|
private val taskExecutor: TaskExecutor)
|
||||||
: RelationService {
|
: RelationService {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): RelationService
|
||||||
|
}
|
||||||
|
|
||||||
override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
|
override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
|
||||||
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
||||||
.also {
|
.also {
|
||||||
saveLocalEcho(it)
|
saveLocalEcho(it)
|
||||||
}
|
}
|
||||||
val sendRelationWork = createSendRelationWork(event)
|
val sendRelationWork = createSendEventWork(event, true)
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
|
TimelineSendEventWorkCommon.postWork(context, roomId, sendRelationWork)
|
||||||
return CancelableWork(context, sendRelationWork.id)
|
return CancelableWork(context, sendRelationWork.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendRelationWork(event: Event): OneTimeWorkRequest {
|
|
||||||
return createSendEventWork(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ {
|
override fun undoReaction(reaction: String, targetEventId: String, myUserId: String)/*: Cancelable*/ {
|
||||||
|
|
||||||
val params = FindReactionEventForUndoTask.Params(
|
val params = FindReactionEventForUndoTask.Params(
|
||||||
@ -128,42 +130,42 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
.also {
|
.also {
|
||||||
saveLocalEcho(it)
|
saveLocalEcho(it)
|
||||||
}
|
}
|
||||||
if (cryptoService.isRoomEncrypted(roomId)) {
|
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||||
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
||||||
val workRequest = createSendEventWork(event)
|
val workRequest = createSendEventWork(event, false)
|
||||||
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
|
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
|
||||||
return CancelableWork(context, encryptWork.id)
|
CancelableWork(context, encryptWork.id)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val workRequest = createSendEventWork(event)
|
val workRequest = createSendEventWork(event, true)
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
|
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
|
||||||
return CancelableWork(context, workRequest.id)
|
CancelableWork(context, workRequest.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editReply(replyToEdit: TimelineEvent,
|
override fun editReply(replyToEdit: TimelineEvent,
|
||||||
originalEvent: TimelineEvent,
|
originalTimelineEvent: TimelineEvent,
|
||||||
newBodyText: String,
|
newBodyText: String,
|
||||||
compatibilityBodyText: String): Cancelable {
|
compatibilityBodyText: String): Cancelable {
|
||||||
val event = eventFactory
|
val event = eventFactory
|
||||||
.createReplaceTextOfReply(roomId,
|
.createReplaceTextOfReply(roomId,
|
||||||
replyToEdit,
|
replyToEdit,
|
||||||
originalEvent,
|
originalTimelineEvent,
|
||||||
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
|
newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText)
|
||||||
.also {
|
.also {
|
||||||
saveLocalEcho(it)
|
saveLocalEcho(it)
|
||||||
}
|
}
|
||||||
if (cryptoService.isRoomEncrypted(roomId)) {
|
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||||
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
||||||
val workRequest = createSendEventWork(event)
|
val workRequest = createSendEventWork(event, false)
|
||||||
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
|
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
|
||||||
return CancelableWork(context, encryptWork.id)
|
CancelableWork(context, encryptWork.id)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val workRequest = createSendEventWork(event)
|
val workRequest = createSendEventWork(event, true)
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
|
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
|
||||||
return CancelableWork(context, workRequest.id)
|
CancelableWork(context, workRequest.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,16 +183,16 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
saveLocalEcho(it)
|
saveLocalEcho(it)
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
if (cryptoService.isRoomEncrypted(roomId)) {
|
return if (cryptoService.isRoomEncrypted(roomId)) {
|
||||||
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
||||||
val workRequest = createSendEventWork(event)
|
val workRequest = createSendEventWork(event, false)
|
||||||
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
|
TimelineSendEventWorkCommon.postSequentialWorks(context, roomId, encryptWork, workRequest)
|
||||||
return CancelableWork(context, encryptWork.id)
|
CancelableWork(context, encryptWork.id)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val workRequest = createSendEventWork(event)
|
val workRequest = createSendEventWork(event, true)
|
||||||
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
|
TimelineSendEventWorkCommon.postWork(context, roomId, workRequest)
|
||||||
return CancelableWork(context, workRequest.id)
|
CancelableWork(context, workRequest.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -202,10 +204,10 @@ internal class DefaultRelationService @Inject constructor(private val context: C
|
|||||||
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
return TimelineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event): OneTimeWorkRequest {
|
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
val sendContentWorkerParams = SendEventWorker.Params(credentials.userId, roomId, event)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, true)
|
return TimelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
|
override fun getEventSummaryLive(eventId: String): LiveData<EventAnnotationsSummary> {
|
||||||
|
@ -17,12 +17,22 @@
|
|||||||
package im.vector.matrix.android.internal.session.room.send
|
package im.vector.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.*
|
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 com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.events.model.*
|
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.MessageContent
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
import im.vector.matrix.android.api.session.room.send.SendService
|
||||||
@ -47,18 +57,22 @@ import im.vector.matrix.android.internal.worker.startChain
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
private const val UPLOAD_WORK = "UPLOAD_WORK"
|
private const val UPLOAD_WORK = "UPLOAD_WORK"
|
||||||
private const val BACKOFF_DELAY = 10_000L
|
private const val BACKOFF_DELAY = 10_000L
|
||||||
|
|
||||||
internal class DefaultSendService @Inject constructor(private val context: Context,
|
internal class DefaultSendService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
|
private val context: Context,
|
||||||
private val credentials: Credentials,
|
private val credentials: Credentials,
|
||||||
private val roomId: String,
|
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val monarchy: Monarchy)
|
private val monarchy: Monarchy
|
||||||
: SendService {
|
) : SendService {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): SendService
|
||||||
|
}
|
||||||
|
|
||||||
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
private val workerFutureListenerExecutor = Executors.newSingleThreadExecutor()
|
||||||
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
|
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
|
||||||
|
@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
|
|||||||
import im.vector.matrix.android.internal.util.StringProvider
|
import im.vector.matrix.android.internal.util.StringProvider
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import org.commonmark.renderer.html.HtmlRenderer
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,17 +304,22 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
|
||||||
|
return buildString {
|
||||||
|
append("> <")
|
||||||
|
append(originalSenderId)
|
||||||
|
append(">")
|
||||||
|
|
||||||
val lines = body.text.split("\n")
|
val lines = body.text.split("\n")
|
||||||
val replyFallback = StringBuffer("> <$originalSenderId>")
|
|
||||||
lines.forEachIndexed { index, s ->
|
lines.forEachIndexed { index, s ->
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
replyFallback.append(" $s")
|
append(" $s")
|
||||||
} else {
|
} else {
|
||||||
replyFallback.append("\n> $s")
|
append("\n> $s")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replyFallback.append("\n\n").append(newBodyText)
|
append("\n\n")
|
||||||
return replyFallback.toString()
|
append(newBodyText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package im.vector.matrix.android.internal.session.room.state
|
package im.vector.matrix.android.internal.session.room.state
|
||||||
|
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
@ -29,13 +31,18 @@ import im.vector.matrix.android.internal.task.TaskExecutor
|
|||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class DefaultStateService @Inject constructor(private val roomId: String,
|
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
@SessionDatabase
|
@SessionDatabase
|
||||||
private val realmConfiguration: RealmConfiguration,
|
private val realmConfiguration: RealmConfiguration,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val sendStateTask: SendStateTask) : StateService {
|
private val sendStateTask: SendStateTask
|
||||||
|
) : StateService {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): StateService
|
||||||
|
}
|
||||||
|
|
||||||
override fun getStateEvent(eventType: String): Event? {
|
override fun getStateEvent(eventType: String): Event? {
|
||||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||||
|
@ -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.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||||
import im.vector.matrix.android.api.session.room.send.SendState
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.api.util.CancelableBag
|
import im.vector.matrix.android.api.util.CancelableBag
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.TimelineEventMapper
|
||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.*
|
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.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.TaskConstraints
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.Debouncer
|
import im.vector.matrix.android.internal.util.Debouncer
|
||||||
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
import im.vector.matrix.android.internal.util.createBackgroundHandler
|
||||||
import im.vector.matrix.android.internal.util.createUIHandler
|
import im.vector.matrix.android.internal.util.createUIHandler
|
||||||
import io.realm.*
|
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 timber.log.Timber
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@ -42,7 +62,6 @@ import kotlin.collections.ArrayList
|
|||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
|
||||||
private const val INITIAL_LOAD_SIZE = 30
|
|
||||||
private const val MIN_FETCHING_COUNT = 30
|
private const val MIN_FETCHING_COUNT = 30
|
||||||
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
|
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
|
||||||
|
|
||||||
@ -53,9 +72,11 @@ internal class DefaultTimeline(
|
|||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val paginationTask: PaginationTask,
|
private val paginationTask: PaginationTask,
|
||||||
cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val allowedTypes: List<String>?
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
) : Timeline {
|
private val settings: TimelineSettings,
|
||||||
|
private val hiddenReadReceipts: TimelineHiddenReadReceipts
|
||||||
|
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
||||||
@ -77,6 +98,8 @@ internal class DefaultTimeline(
|
|||||||
private val debouncer = Debouncer(mainHandler)
|
private val debouncer = Debouncer(mainHandler)
|
||||||
|
|
||||||
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
private lateinit var liveEvents: RealmResults<TimelineEventEntity>
|
||||||
|
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
||||||
|
|
||||||
private var roomEntity: RoomEntity? = null
|
private var roomEntity: RoomEntity? = null
|
||||||
|
|
||||||
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
private var prevDisplayIndex: Int = DISPLAY_INDEX_UNKNOWN
|
||||||
@ -87,11 +110,8 @@ internal class DefaultTimeline(
|
|||||||
private val backwardsPaginationState = AtomicReference(PaginationState())
|
private val backwardsPaginationState = AtomicReference(PaginationState())
|
||||||
private val forwardsPaginationState = AtomicReference(PaginationState())
|
private val forwardsPaginationState = AtomicReference(PaginationState())
|
||||||
|
|
||||||
|
|
||||||
private val timelineID = UUID.randomUUID().toString()
|
private val timelineID = UUID.randomUUID().toString()
|
||||||
|
|
||||||
private lateinit var eventRelations: RealmResults<EventAnnotationsSummaryEntity>
|
|
||||||
|
|
||||||
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
private val eventDecryptor = TimelineEventDecryptor(realmConfiguration, timelineID, cryptoService)
|
||||||
|
|
||||||
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
|
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<TimelineEventEntity>> { results, changeSet ->
|
||||||
@ -130,9 +150,9 @@ internal class DefaultTimeline(
|
|||||||
val eventEntity = results[index]
|
val eventEntity = results[index]
|
||||||
eventEntity?.eventId?.let { eventId ->
|
eventEntity?.eventId?.let { eventId ->
|
||||||
builtEventsIdMap[eventId]?.let { builtIndex ->
|
builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
//Update the relation of existing event
|
//Update an existing event
|
||||||
builtEvents[builtIndex]?.let { te ->
|
builtEvents[builtIndex]?.let { te ->
|
||||||
builtEvents[builtIndex] = eventEntity.asDomain()
|
builtEvents[builtIndex] = buildTimelineEvent(eventEntity)
|
||||||
hasChanged = true
|
hasChanged = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,34 +182,8 @@ internal class DefaultTimeline(
|
|||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// private val newSessionListener = object : NewSessionListener {
|
|
||||||
// override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
|
||||||
// if (roomId == this@DefaultTimeline.roomId) {
|
|
||||||
// Timber.v("New session id detected for this room")
|
|
||||||
// BACKGROUND_HANDLER.post {
|
|
||||||
// val realm = backgroundRealm.get()
|
|
||||||
// var hasChange = false
|
|
||||||
// builtEvents.forEachIndexed { index, timelineEvent ->
|
|
||||||
// if (timelineEvent.isEncrypted()) {
|
|
||||||
// val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
|
|
||||||
// if (eventContent?.sessionId == sessionId
|
|
||||||
// && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
|
|
||||||
// //we need to rebuild this event
|
|
||||||
// EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
|
|
||||||
// //builtEvents[index] = timelineEventFactory.create(it, realm)
|
|
||||||
// hasChange = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (hasChange) postSnapshot()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Public methods ******************************************************************************
|
// Public methods ******************************************************************************
|
||||||
|
|
||||||
override fun paginate(direction: Timeline.Direction, count: Int) {
|
override fun paginate(direction: Timeline.Direction, count: Int) {
|
||||||
BACKGROUND_HANDLER.post {
|
BACKGROUND_HANDLER.post {
|
||||||
@ -234,15 +228,20 @@ internal class DefaultTimeline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
liveEvents = buildEventQuery(realm)
|
liveEvents = buildEventQuery(realm)
|
||||||
|
.filterEventsWithSettings()
|
||||||
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
.sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.DESCENDING)
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
.also { it.addChangeListener(eventsChangeListener) }
|
.also { it.addChangeListener(eventsChangeListener) }
|
||||||
|
|
||||||
isReady.set(true)
|
|
||||||
|
|
||||||
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
eventRelations = EventAnnotationsSummaryEntity.whereInRoom(realm, roomId)
|
||||||
.findAllAsync()
|
.findAllAsync()
|
||||||
.also { it.addChangeListener(relationsListener) }
|
.also { it.addChangeListener(relationsListener) }
|
||||||
|
|
||||||
|
if (settings.buildReadReceipts) {
|
||||||
|
hiddenReadReceipts.start(realm, liveEvents, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
isReady.set(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,6 +255,9 @@ internal class DefaultTimeline(
|
|||||||
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
roomEntity?.sendingTimelineEvents?.removeAllChangeListeners()
|
||||||
eventRelations.removeAllChangeListeners()
|
eventRelations.removeAllChangeListeners()
|
||||||
liveEvents.removeAllChangeListeners()
|
liveEvents.removeAllChangeListeners()
|
||||||
|
if (settings.buildReadReceipts) {
|
||||||
|
hiddenReadReceipts.dispose()
|
||||||
|
}
|
||||||
backgroundRealm.getAndSet(null).also {
|
backgroundRealm.getAndSet(null).also {
|
||||||
it.close()
|
it.close()
|
||||||
}
|
}
|
||||||
@ -267,6 +269,22 @@ internal class DefaultTimeline(
|
|||||||
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
return hasMoreInCache(direction) || !hasReachedEnd(direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TimelineHiddenReadReceipts.Delegate
|
||||||
|
|
||||||
|
override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
|
||||||
|
return builtEventsIdMap[eventId]?.let { builtIndex ->
|
||||||
|
//Update the relation of existing event
|
||||||
|
builtEvents[builtIndex]?.let { te ->
|
||||||
|
builtEvents[builtIndex] = te.copy(readReceipts = readReceipts)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReadReceiptsUpdated() {
|
||||||
|
postSnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
|
||||||
@ -329,9 +347,11 @@ internal class DefaultTimeline(
|
|||||||
val sendingEvents = ArrayList<TimelineEvent>()
|
val sendingEvents = ArrayList<TimelineEvent>()
|
||||||
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
if (hasReachedEnd(Timeline.Direction.FORWARDS)) {
|
||||||
roomEntity?.sendingTimelineEvents
|
roomEntity?.sendingTimelineEvents
|
||||||
?.filter { allowedTypes?.contains(it.root?.type) ?: false }
|
?.where()
|
||||||
|
?.filterEventsWithSettings()
|
||||||
|
?.findAll()
|
||||||
?.forEach {
|
?.forEach {
|
||||||
sendingEvents.add(it.asDomain())
|
sendingEvents.add(timelineEventMapper.map(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sendingEvents
|
return sendingEvents
|
||||||
@ -378,7 +398,7 @@ internal class DefaultTimeline(
|
|||||||
if (initialEventId != null && shouldFetchInitialEvent) {
|
if (initialEventId != null && shouldFetchInitialEvent) {
|
||||||
fetchEvent(initialEventId)
|
fetchEvent(initialEventId)
|
||||||
} else {
|
} else {
|
||||||
val count = Math.min(INITIAL_LOAD_SIZE, liveEvents.size)
|
val count = Math.min(settings.initialSize, liveEvents.size)
|
||||||
if (isLive) {
|
if (isLive) {
|
||||||
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count)
|
||||||
} else {
|
} else {
|
||||||
@ -463,7 +483,8 @@ internal class DefaultTimeline(
|
|||||||
nextDisplayIndex = offsetIndex + 1
|
nextDisplayIndex = offsetIndex + 1
|
||||||
}
|
}
|
||||||
offsetResults.forEach { eventEntity ->
|
offsetResults.forEach { eventEntity ->
|
||||||
val timelineEvent = eventEntity.asDomain()
|
|
||||||
|
val timelineEvent = buildTimelineEvent(eventEntity)
|
||||||
|
|
||||||
if (timelineEvent.isEncrypted()
|
if (timelineEvent.isEncrypted()
|
||||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||||
@ -481,6 +502,12 @@ internal class DefaultTimeline(
|
|||||||
return offsetResults.size
|
return offsetResults.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map(
|
||||||
|
timelineEventEntity = eventEntity,
|
||||||
|
buildReadReceipts = settings.buildReadReceipts,
|
||||||
|
correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This has to be called on TimelineThread as it access realm live results
|
* This has to be called on TimelineThread as it access realm live results
|
||||||
*/
|
*/
|
||||||
@ -498,7 +525,6 @@ internal class DefaultTimeline(
|
|||||||
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
.greaterThanOrEqualTo(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, startDisplayIndex)
|
||||||
}
|
}
|
||||||
return offsetQuery
|
return offsetQuery
|
||||||
.filterAllowedTypes()
|
|
||||||
.limit(count)
|
.limit(count)
|
||||||
.findAll()
|
.findAll()
|
||||||
}
|
}
|
||||||
@ -545,7 +571,7 @@ internal class DefaultTimeline(
|
|||||||
debouncer.debounce("post_snapshot", runnable, 50)
|
debouncer.debounce("post_snapshot", runnable, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extension methods ***************************************************************************
|
// Extension methods ***************************************************************************
|
||||||
|
|
||||||
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
|
||||||
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
|
||||||
@ -557,16 +583,20 @@ internal class DefaultTimeline(
|
|||||||
} else {
|
} else {
|
||||||
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
sort(TimelineEventEntityFields.ROOT.DISPLAY_INDEX, Sort.ASCENDING)
|
||||||
}
|
}
|
||||||
.filterAllowedTypes()
|
.filterEventsWithSettings()
|
||||||
.findFirst()
|
.findFirst()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RealmQuery<TimelineEventEntity>.filterAllowedTypes(): RealmQuery<TimelineEventEntity> {
|
private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> {
|
||||||
if (allowedTypes != null) {
|
if (settings.filterTypes) {
|
||||||
`in`(TimelineEventEntityFields.ROOT.TYPE, allowedTypes.toTypedArray())
|
`in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray())
|
||||||
|
}
|
||||||
|
if (settings.filterEdits) {
|
||||||
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, FilterContent.EDIT_TYPE)
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class PaginationState(
|
private data class PaginationState(
|
||||||
|
@ -18,28 +18,38 @@ package im.vector.matrix.android.internal.session.room.timeline
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
|
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
|
||||||
import im.vector.matrix.android.internal.database.RealmLiveData
|
import im.vector.matrix.android.internal.database.RealmLiveData
|
||||||
import im.vector.matrix.android.internal.database.mapper.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.model.TimelineEventEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.util.fetchCopyMap
|
import im.vector.matrix.android.internal.util.fetchCopyMap
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
|
internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val monarchy: Monarchy,
|
private val monarchy: Monarchy,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val contextOfEventTask: GetContextOfEventTask,
|
private val contextOfEventTask: GetContextOfEventTask,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val paginationTask: PaginationTask
|
private val paginationTask: PaginationTask,
|
||||||
|
private val timelineEventMapper: TimelineEventMapper,
|
||||||
|
private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper
|
||||||
) : TimelineService {
|
) : TimelineService {
|
||||||
|
|
||||||
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,
|
return DefaultTimeline(roomId,
|
||||||
eventId,
|
eventId,
|
||||||
monarchy.realmConfiguration,
|
monarchy.realmConfiguration,
|
||||||
@ -47,7 +57,10 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
|||||||
contextOfEventTask,
|
contextOfEventTask,
|
||||||
paginationTask,
|
paginationTask,
|
||||||
cryptoService,
|
cryptoService,
|
||||||
allowedTypes)
|
timelineEventMapper,
|
||||||
|
settings,
|
||||||
|
TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
|
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
|
||||||
@ -55,7 +68,7 @@ internal class DefaultTimelineService @Inject constructor(private val roomId: St
|
|||||||
.fetchCopyMap({
|
.fetchCopyMap({
|
||||||
TimelineEventEntity.where(it, eventId = eventId).findFirst()
|
TimelineEventEntity.where(it, eventId = eventId).findFirst()
|
||||||
}, { entity, realm ->
|
}, { 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) {
|
val liveData = RealmLiveData(monarchy.realmConfiguration) {
|
||||||
TimelineEventEntity.where(it, eventId = eventId)
|
TimelineEventEntity.where(it, eventId = eventId)
|
||||||
}
|
}
|
||||||
return Transformations.map(liveData) {
|
return Transformations.map(liveData) { events ->
|
||||||
it.firstOrNull()?.asDomain()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -22,7 +22,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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
||||||
@ -33,7 +33,7 @@ import timber.log.Timber
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: CryptoManager,
|
internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService,
|
||||||
private val sasVerificationService: DefaultSasVerificationService) {
|
private val sasVerificationService: DefaultSasVerificationService) {
|
||||||
|
|
||||||
fun handleToDevice(toDevice: ToDeviceSyncResponse, initialSyncProgressService: DefaultInitialSyncProgressService? = null) {
|
fun handleToDevice(toDevice: ToDeviceSyncResponse, initialSyncProgressService: DefaultInitialSyncProgressService? = null) {
|
||||||
@ -47,13 +47,13 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
|
|||||||
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
|
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
|
||||||
} else {
|
} else {
|
||||||
sasVerificationService.onToDeviceEvent(event)
|
sasVerificationService.onToDeviceEvent(event)
|
||||||
cryptoManager.onToDeviceEvent(event)
|
cryptoService.onToDeviceEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||||
cryptoManager.onSyncCompleted(syncResponse)
|
cryptoService.onSyncCompleted(syncResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
|
|||||||
if (event.getClearType() == EventType.ENCRYPTED) {
|
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||||
var result: MXEventDecryptionResult? = null
|
var result: MXEventDecryptionResult? = null
|
||||||
try {
|
try {
|
||||||
result = cryptoManager.decryptEvent(event, timelineId ?: "")
|
result = cryptoService.decryptEvent(event, timelineId ?: "")
|
||||||
} catch (exception: MXCryptoError) {
|
} catch (exception: MXCryptoError) {
|
||||||
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError)
|
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@
|
|||||||
package im.vector.matrix.android.internal.session.sync
|
package im.vector.matrix.android.internal.session.sync
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
|
||||||
|
import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.createUnmanaged
|
||||||
|
import im.vector.matrix.android.internal.database.query.getOrCreate
|
||||||
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -29,34 +33,70 @@ import javax.inject.Inject
|
|||||||
// dict value ts value
|
// dict value ts value
|
||||||
typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, Double>>>>
|
typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, Double>>>>
|
||||||
|
|
||||||
|
private const val READ_KEY = "m.read"
|
||||||
|
private const val TIMESTAMP_KEY = "ts"
|
||||||
|
|
||||||
internal class ReadReceiptHandler @Inject constructor() {
|
internal class ReadReceiptHandler @Inject constructor() {
|
||||||
|
|
||||||
fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?) {
|
fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) {
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val readReceipts = mapContentToReadReceiptEntities(roomId, content)
|
handleReadReceiptContent(realm, roomId, content, isInitialSync)
|
||||||
realm.insertOrUpdate(readReceipts)
|
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Timber.e("Fail to handle read receipt for room $roomId")
|
Timber.e("Fail to handle read receipt for room $roomId")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapContentToReadReceiptEntities(roomId: String, content: ReadReceiptContent): List<ReadReceiptEntity> {
|
private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) {
|
||||||
return content
|
if (isInitialSync) {
|
||||||
.flatMap { (eventId, receiptDict) ->
|
initialSyncStrategy(realm, roomId, content)
|
||||||
receiptDict
|
} else {
|
||||||
.filterKeys { it == "m.read" }
|
incrementalSyncStrategy(realm, roomId, content)
|
||||||
.flatMap { (_, userIdsDict) ->
|
}
|
||||||
userIdsDict.map { (userId, paramsDict) ->
|
}
|
||||||
val ts = paramsDict.filterKeys { it == "ts" }
|
|
||||||
.values
|
|
||||||
.firstOrNull() ?: 0.0
|
private fun initialSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
|
||||||
val primaryKey = roomId + userId
|
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>()
|
||||||
ReadReceiptEntity(primaryKey, userId, eventId, roomId, ts)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
|
|||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
|
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.database.helper.*
|
import im.vector.matrix.android.internal.database.helper.*
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
import im.vector.matrix.android.internal.database.model.EventEntityFields
|
||||||
@ -50,7 +50,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
private val readReceiptHandler: ReadReceiptHandler,
|
private val readReceiptHandler: ReadReceiptHandler,
|
||||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||||
private val roomTagHandler: RoomTagHandler,
|
private val roomTagHandler: RoomTagHandler,
|
||||||
private val cryptoManager: CryptoManager,
|
private val cryptoService: DefaultCryptoService,
|
||||||
private val tokenStore: SyncTokenStore,
|
private val tokenStore: SyncTokenStore,
|
||||||
private val pushRuleService: DefaultPushRuleService,
|
private val pushRuleService: DefaultPushRuleService,
|
||||||
private val processForPushTask: ProcessEventForPushTask,
|
private val processForPushTask: ProcessEventForPushTask,
|
||||||
@ -62,11 +62,11 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
|
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handle(roomsSyncResponse: RoomsSyncResponse, reporter: DefaultInitialSyncProgressService? = null) {
|
fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter)
|
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter)
|
||||||
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter)
|
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter)
|
||||||
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter)
|
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter)
|
||||||
}
|
}
|
||||||
|
|
||||||
//handle event for bing rule checks
|
//handle event for bing rule checks
|
||||||
@ -89,20 +89,20 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
|
|
||||||
// PRIVATE METHODS *****************************************************************************
|
// 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) {
|
val rooms = when (handlingStrategy) {
|
||||||
is HandlingStrategy.JOINED ->
|
is HandlingStrategy.JOINED ->
|
||||||
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) {
|
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_joined_rooms, 0.6f) {
|
||||||
handleJoinedRoom(realm, it.key, it.value)
|
handleJoinedRoom(realm, it.key, it.value, isInitialSync)
|
||||||
}
|
}
|
||||||
is HandlingStrategy.INVITED ->
|
is HandlingStrategy.INVITED ->
|
||||||
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.4f) {
|
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.1f) {
|
||||||
handleInvitedRoom(realm, it.key, it.value)
|
handleInvitedRoom(realm, it.key, it.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
is HandlingStrategy.LEFT -> {
|
is HandlingStrategy.LEFT -> {
|
||||||
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.2f) {
|
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.3f) {
|
||||||
handleLeftRoom(realm, it.key, it.value)
|
handleLeftRoom(realm, it.key, it.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,12 +112,20 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
|
|
||||||
private fun handleJoinedRoom(realm: Realm,
|
private fun handleJoinedRoom(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
roomSync: RoomSync): RoomEntity {
|
roomSync: RoomSync,
|
||||||
|
isInitalSync: Boolean): RoomEntity {
|
||||||
|
|
||||||
Timber.v("Handle join sync for room $roomId")
|
Timber.v("Handle join sync for room $roomId")
|
||||||
|
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
|
||||||
?: realm.createObject(roomId)
|
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)
|
||||||
|
|
||||||
if (roomEntity.membership == Membership.INVITE) {
|
if (roomEntity.membership == Membership.INVITE) {
|
||||||
roomEntity.chunks.deleteAllFromRealm()
|
roomEntity.chunks.deleteAllFromRealm()
|
||||||
@ -126,13 +134,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
|
|
||||||
// State event
|
// State event
|
||||||
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
|
||||||
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
|
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() ?: Int.MIN_VALUE
|
||||||
?: Int.MIN_VALUE
|
|
||||||
val untimelinedStateIndex = minStateIndex + 1
|
val untimelinedStateIndex = minStateIndex + 1
|
||||||
roomSync.state.events.forEach { event ->
|
roomSync.state.events.forEach { event ->
|
||||||
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
|
||||||
// Give info to crypto module
|
// Give info to crypto module
|
||||||
cryptoManager.onStateEvent(roomId, event)
|
cryptoService.onStateEvent(roomId, event)
|
||||||
UserEntityFactory.createOrNull(event)?.also {
|
UserEntityFactory.createOrNull(event)?.also {
|
||||||
realm.insertOrUpdate(it)
|
realm.insertOrUpdate(it)
|
||||||
}
|
}
|
||||||
@ -150,14 +157,6 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
roomEntity.addOrUpdate(chunkEntity)
|
roomEntity.addOrUpdate(chunkEntity)
|
||||||
}
|
}
|
||||||
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)
|
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)
|
||||||
|
|
||||||
if (roomSync.ephemeral != null && roomSync.ephemeral.events.isNotEmpty()) {
|
|
||||||
handleEphemeral(realm, roomId, roomSync.ephemeral)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roomSync.accountData != null && roomSync.accountData.events.isNullOrEmpty().not()) {
|
|
||||||
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
|
|
||||||
}
|
|
||||||
return roomEntity
|
return roomEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,8 +165,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
roomSync:
|
roomSync:
|
||||||
InvitedRoomSync): RoomEntity {
|
InvitedRoomSync): RoomEntity {
|
||||||
Timber.v("Handle invited sync for room $roomId")
|
Timber.v("Handle invited sync for room $roomId")
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||||
?: realm.createObject(roomId)
|
|
||||||
roomEntity.membership = Membership.INVITE
|
roomEntity.membership = Membership.INVITE
|
||||||
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
|
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
|
||||||
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
|
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
|
||||||
@ -180,8 +178,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
private fun handleLeftRoom(realm: Realm,
|
private fun handleLeftRoom(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
roomSync: RoomSync): RoomEntity {
|
roomSync: RoomSync): RoomEntity {
|
||||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
|
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||||
?: realm.createObject(roomId)
|
|
||||||
|
|
||||||
roomEntity.membership = Membership.LEAVE
|
roomEntity.membership = Membership.LEAVE
|
||||||
roomEntity.chunks.deleteAllFromRealm()
|
roomEntity.chunks.deleteAllFromRealm()
|
||||||
@ -213,7 +210,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
event.eventId?.also { eventIds.add(it) }
|
event.eventId?.also { eventIds.add(it) }
|
||||||
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
|
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
|
||||||
// Give info to crypto module
|
// Give info to crypto module
|
||||||
cryptoManager.onLiveEvent(roomEntity.roomId, event)
|
cryptoService.onLiveEvent(roomEntity.roomId, event)
|
||||||
// Try to remove local echo
|
// Try to remove local echo
|
||||||
event.unsignedData?.transactionId?.also {
|
event.unsignedData?.transactionId?.also {
|
||||||
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
|
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)
|
||||||
@ -233,17 +230,21 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun handleEphemeral(realm: Realm,
|
private fun handleEphemeral(realm: Realm,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
ephemeral: RoomSyncEphemeral) {
|
ephemeral: RoomSyncEphemeral,
|
||||||
ephemeral.events
|
isInitalSync: Boolean) {
|
||||||
.filter { it.getClearType() == EventType.RECEIPT }
|
for (event in ephemeral.events) {
|
||||||
.map { it.content.toModel<ReadReceiptContent>() }
|
if (event.type != EventType.RECEIPT) continue
|
||||||
.forEach { readReceiptHandler.handle(realm, roomId, it) }
|
val readReceiptContent = event.content as? ReadReceiptContent ?: continue
|
||||||
|
readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitalSync)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
|
||||||
accountData.events
|
accountData.events
|
||||||
|
.asSequence()
|
||||||
.filter { it.getClearType() == EventType.TAG }
|
.filter { it.getClearType() == EventType.TAG }
|
||||||
.map { it.content.toModel<RoomTagContent>() }
|
.map { it.content.toModel<RoomTagContent>() }
|
||||||
.forEach { roomTagHandler.handle(realm, roomId, it) }
|
.forEach { roomTagHandler.handle(realm, roomId, it) }
|
||||||
|
@ -21,7 +21,6 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
|||||||
import im.vector.matrix.android.internal.database.model.RoomTagEntity
|
import im.vector.matrix.android.internal.database.model.RoomTagEntity
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomTagHandler @Inject constructor() {
|
internal class RoomTagHandler @Inject constructor() {
|
||||||
@ -30,16 +29,8 @@ internal class RoomTagHandler @Inject constructor() {
|
|||||||
if (content == null) {
|
if (content == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val tags = ArrayList<RoomTagEntity>()
|
val tags = content.tags.entries.map { (tagName, params) ->
|
||||||
for (tagName in content.tags.keys) {
|
RoomTagEntity(tagName, params["order"] as? Double)
|
||||||
val params = content.tags[tagName]
|
|
||||||
val order = params?.get("order")
|
|
||||||
val tag = if (order is Double) {
|
|
||||||
RoomTagEntity(tagName, order)
|
|
||||||
} else {
|
|
||||||
RoomTagEntity(tagName, null)
|
|
||||||
}
|
|
||||||
tags.add(tag)
|
|
||||||
}
|
}
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: RoomSummaryEntity(roomId)
|
?: RoomSummaryEntity(roomId)
|
||||||
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.sync
|
|||||||
|
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
import im.vector.matrix.android.R
|
import im.vector.matrix.android.R
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
|
||||||
import im.vector.matrix.android.internal.session.reportSubtask
|
import im.vector.matrix.android.internal.session.reportSubtask
|
||||||
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||||
@ -30,7 +30,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
|
|||||||
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
|
||||||
private val groupSyncHandler: GroupSyncHandler,
|
private val groupSyncHandler: GroupSyncHandler,
|
||||||
private val cryptoSyncHandler: CryptoSyncHandler,
|
private val cryptoSyncHandler: CryptoSyncHandler,
|
||||||
private val cryptoManager: CryptoManager,
|
private val cryptoService: DefaultCryptoService,
|
||||||
private val initialSyncProgressService: DefaultInitialSyncProgressService) {
|
private val initialSyncProgressService: DefaultInitialSyncProgressService) {
|
||||||
|
|
||||||
fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> {
|
fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> {
|
||||||
@ -40,12 +40,12 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
|
|||||||
val reporter = initialSyncProgressService.takeIf { isInitialSync }
|
val reporter = initialSyncProgressService.takeIf { isInitialSync }
|
||||||
|
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
if (!cryptoManager.isStarted()) {
|
if (!cryptoService.isStarted()) {
|
||||||
Timber.v("Should start cryptoManager")
|
Timber.v("Should start cryptoService")
|
||||||
cryptoManager.start(isInitialSync)
|
cryptoService.start(isInitialSync)
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
Timber.v("Finish handling start cryptoManager in $it ms")
|
Timber.v("Finish handling start cryptoService in $it ms")
|
||||||
}
|
}
|
||||||
val measure = measureTimeMillis {
|
val measure = measureTimeMillis {
|
||||||
// Handle the to device events before the room ones
|
// Handle the to device events before the room ones
|
||||||
@ -66,7 +66,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
|
|||||||
|
|
||||||
reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) {
|
reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) {
|
||||||
if (syncResponse.rooms != null) {
|
if (syncResponse.rooms != null) {
|
||||||
roomSyncHandler.handle(syncResponse.rooms, reporter)
|
roomSyncHandler.handle(syncResponse.rooms, isInitialSync, reporter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
}.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.TaskThread
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
@ -70,6 +71,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
if (state is SyncState.RUNNING) {
|
if (state is SyncState.RUNNING) {
|
||||||
Timber.v("Pause sync...")
|
Timber.v("Pause sync...")
|
||||||
updateStateTo(SyncState.PAUSED)
|
updateStateTo(SyncState.PAUSED)
|
||||||
|
cancelableTask?.cancel()
|
||||||
|
lock.notify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,18 +93,25 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
backgroundDetectionObserver.register(this)
|
backgroundDetectionObserver.register(this)
|
||||||
|
|
||||||
while (state != SyncState.KILLING) {
|
while (state != SyncState.KILLING) {
|
||||||
|
Timber.v("Entering loop, state: $state")
|
||||||
|
|
||||||
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
|
if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) {
|
||||||
Timber.v("Sync is Paused. Waiting...")
|
Timber.v("No network or sync is Paused. Waiting...")
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
lock.wait()
|
lock.wait()
|
||||||
}
|
}
|
||||||
|
Timber.v("...unlocked")
|
||||||
} else {
|
} else {
|
||||||
if (state !is SyncState.RUNNING) {
|
if (state !is SyncState.RUNNING) {
|
||||||
updateStateTo(SyncState.RUNNING(afterPause = true))
|
updateStateTo(SyncState.RUNNING(afterPause = true))
|
||||||
}
|
}
|
||||||
Timber.v("[$this] Execute sync request with timeout $DEFAULT_LONG_POOL_TIMEOUT")
|
|
||||||
|
// No timeout after a pause
|
||||||
|
val timeout = state.let { if (it is SyncState.RUNNING && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
||||||
|
|
||||||
|
Timber.v("Execute sync request with timeout $timeout")
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val params = SyncTask.Params(DEFAULT_LONG_POOL_TIMEOUT)
|
val params = SyncTask.Params(timeout)
|
||||||
|
|
||||||
cancelableTask = syncTask.configureWith(params) {
|
cancelableTask = syncTask.configureWith(params) {
|
||||||
this.callbackThread = TaskThread.SYNC
|
this.callbackThread = TaskThread.SYNC
|
||||||
@ -109,29 +119,31 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
this.callback = object : MatrixCallback<Unit> {
|
this.callback = object : MatrixCallback<Unit> {
|
||||||
|
|
||||||
override fun onSuccess(data: Unit) {
|
override fun onSuccess(data: Unit) {
|
||||||
|
Timber.v("onSuccess")
|
||||||
latch.countDown()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
if (failure is Failure.NetworkConnection
|
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
|
||||||
&& failure.cause is SocketTimeoutException) {
|
|
||||||
// Timeout are not critical
|
// Timeout are not critical
|
||||||
Timber.v("Timeout")
|
Timber.v("Timeout")
|
||||||
} else {
|
} else if (failure is Failure.Unknown && failure.throwable is CancellationException) {
|
||||||
Timber.e(failure)
|
Timber.v("Cancelled")
|
||||||
}
|
} else if (failure is Failure.ServerError
|
||||||
|
|
||||||
if (failure !is Failure.NetworkConnection
|
|
||||||
|| failure.cause is JsonEncodingException) {
|
|
||||||
// Wait 10s before retrying
|
|
||||||
sleep(RETRY_WAIT_TIME_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failure is Failure.ServerError
|
|
||||||
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
|
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
|
||||||
// No token or invalid token, stop the thread
|
// No token or invalid token, stop the thread
|
||||||
|
Timber.w(failure)
|
||||||
updateStateTo(SyncState.KILLING)
|
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()
|
latch.countDown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,9 +151,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
|
||||||
latch.await()
|
latch.await()
|
||||||
if (state is SyncState.RUNNING) {
|
state.let {
|
||||||
|
if (it is SyncState.RUNNING && it.afterPause) {
|
||||||
updateStateTo(SyncState.RUNNING(afterPause = false))
|
updateStateTo(SyncState.RUNNING(afterPause = false))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timber.v("...Continue")
|
Timber.v("...Continue")
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.util
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
internal class CancelableWork(private val context: Context,
|
internal class CancelableWork(private val context: Context,
|
||||||
private val workId: UUID) : Cancelable {
|
private val workId: UUID) : Cancelable {
|
||||||
|
@ -34,7 +34,7 @@ import java.security.*
|
|||||||
import java.security.cert.CertificateException
|
import java.security.cert.CertificateException
|
||||||
import java.security.spec.AlgorithmParameterSpec
|
import java.security.spec.AlgorithmParameterSpec
|
||||||
import java.security.spec.RSAKeyGenParameterSpec
|
import java.security.spec.RSAKeyGenParameterSpec
|
||||||
import java.util.*
|
import java.util.Calendar
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
import javax.crypto.*
|
import javax.crypto.*
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
@ -22,7 +22,7 @@ import org.json.JSONArray
|
|||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.*
|
import java.util.TreeSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build canonical Json
|
* Build canonical Json
|
||||||
@ -60,43 +60,33 @@ object JsonCanonicalizer {
|
|||||||
when (any) {
|
when (any) {
|
||||||
is JSONArray -> {
|
is JSONArray -> {
|
||||||
// Canonicalize each element of the array
|
// Canonicalize each element of the array
|
||||||
val result = StringBuilder("[")
|
return (0 until any.length()).joinToString(separator = ",", prefix = "[", postfix = "]") {
|
||||||
|
canonicalizeRecursive(any.get(it))
|
||||||
for (i in 0 until any.length()) {
|
|
||||||
result.append(canonicalizeRecursive(any.get(i)))
|
|
||||||
if (i < any.length() - 1) {
|
|
||||||
result.append(",")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append("]")
|
|
||||||
|
|
||||||
return result.toString()
|
|
||||||
}
|
|
||||||
is JSONObject -> {
|
is JSONObject -> {
|
||||||
// Sort the attributes by name, and the canonicalize each element of the JSONObject
|
// Sort the attributes by name, and the canonicalize each element of the JSONObject
|
||||||
val result = StringBuilder("{")
|
|
||||||
|
|
||||||
val attributes = TreeSet<String>()
|
val attributes = TreeSet<String>()
|
||||||
for (entry in any.keys()) {
|
for (entry in any.keys()) {
|
||||||
attributes.add(entry)
|
attributes.add(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (attribute in attributes.withIndex()) {
|
return buildString {
|
||||||
result.append("\"")
|
append("{")
|
||||||
.append(attribute.value)
|
for ((index, value) in attributes.withIndex()) {
|
||||||
.append("\"")
|
append("\"")
|
||||||
.append(":")
|
append(value)
|
||||||
.append(canonicalizeRecursive(any[attribute.value]))
|
append("\"")
|
||||||
|
append(":")
|
||||||
|
append(canonicalizeRecursive(any[value]))
|
||||||
|
|
||||||
if (attribute.index < attributes.size - 1) {
|
if (index < attributes.size - 1) {
|
||||||
result.append(",")
|
append(",")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
append("}")
|
||||||
result.append("}")
|
}
|
||||||
|
|
||||||
return result.toString()
|
|
||||||
}
|
}
|
||||||
is String -> return JSONObject.quote(any)
|
is String -> return JSONObject.quote(any)
|
||||||
else -> return any.toString()
|
else -> return any.toString()
|
||||||
|
@ -167,4 +167,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Начална синхронизация:
|
<string name="initial_sync_start_importing_account_data">Начална синхронизация:
|
||||||
\nИмпортиране на данни за профила</string>
|
\nИмпортиране на данни за профила</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s обнови тази стая.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Изпращане на съобщение…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Изчисти опашката за изпращане</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -105,4 +105,65 @@
|
|||||||
<string name="verification_emoji_pig">Schwein</string>
|
<string name="verification_emoji_pig">Schwein</string>
|
||||||
<string name="verification_emoji_elephant">Elefant</string>
|
<string name="verification_emoji_elephant">Elefant</string>
|
||||||
<string name="verification_emoji_rabbit">Hase</string>
|
<string name="verification_emoji_rabbit">Hase</string>
|
||||||
|
<string name="notice_room_update">%s hat diesen Raum aufgewertet.</string>
|
||||||
|
|
||||||
|
<string name="verification_emoji_panda">Panda</string>
|
||||||
|
<string name="verification_emoji_rooster">Hahn</string>
|
||||||
|
<string name="verification_emoji_penguin">Pinguin</string>
|
||||||
|
<string name="verification_emoji_turtle">Schildkröte</string>
|
||||||
|
<string name="verification_emoji_fish">Fisch</string>
|
||||||
|
<string name="verification_emoji_octopus">Tintenfisch</string>
|
||||||
|
<string name="verification_emoji_butterfly">Schmetterling</string>
|
||||||
|
<string name="verification_emoji_flower">Blume</string>
|
||||||
|
<string name="verification_emoji_tree">Baum</string>
|
||||||
|
<string name="verification_emoji_cactus">Kaktus</string>
|
||||||
|
<string name="verification_emoji_mushroom">Pilz</string>
|
||||||
|
<string name="verification_emoji_globe">Globus</string>
|
||||||
|
<string name="verification_emoji_moon">Mond</string>
|
||||||
|
<string name="verification_emoji_cloud">Wolke</string>
|
||||||
|
<string name="verification_emoji_fire">Feuer</string>
|
||||||
|
<string name="verification_emoji_banana">Banane</string>
|
||||||
|
<string name="verification_emoji_apple">Apfel</string>
|
||||||
|
<string name="verification_emoji_strawberry">Erdbeere</string>
|
||||||
|
<string name="verification_emoji_corn">Mais</string>
|
||||||
|
<string name="verification_emoji_cake">Kuchen</string>
|
||||||
|
<string name="verification_emoji_heart">Herz</string>
|
||||||
|
<string name="verification_emoji_smiley">Lächeln</string>
|
||||||
|
<string name="verification_emoji_robot">Roboter</string>
|
||||||
|
<string name="verification_emoji_hat">Hut</string>
|
||||||
|
<string name="verification_emoji_glasses">Brille</string>
|
||||||
|
<string name="verification_emoji_wrench">Schraubenschlüssel</string>
|
||||||
|
<string name="verification_emoji_santa">Nikolaus</string>
|
||||||
|
<string name="verification_emoji_thumbsup">Daumen hoch</string>
|
||||||
|
<string name="verification_emoji_umbrella">Regenschirm</string>
|
||||||
|
<string name="verification_emoji_hourglass">Sanduhr</string>
|
||||||
|
<string name="verification_emoji_clock">Uhr</string>
|
||||||
|
<string name="verification_emoji_gift">Geschenk</string>
|
||||||
|
<string name="verification_emoji_lightbulb">Glühbirne</string>
|
||||||
|
<string name="verification_emoji_book">Buch</string>
|
||||||
|
<string name="verification_emoji_pencil">Stift</string>
|
||||||
|
<string name="verification_emoji_paperclip">Büroklammer</string>
|
||||||
|
<string name="verification_emoji_scissors">Scheren</string>
|
||||||
|
<string name="verification_emoji_lock">sperren</string>
|
||||||
|
<string name="verification_emoji_key">Schlüssel</string>
|
||||||
|
<string name="verification_emoji_hammer">Hammer</string>
|
||||||
|
<string name="verification_emoji_telephone">Telefon</string>
|
||||||
|
<string name="verification_emoji_flag">Flagge</string>
|
||||||
|
<string name="verification_emoji_train">Zug</string>
|
||||||
|
<string name="verification_emoji_bicycle">Fahrrad</string>
|
||||||
|
<string name="verification_emoji_airplane">Flugzeug</string>
|
||||||
|
<string name="verification_emoji_rocket">Rakete</string>
|
||||||
|
<string name="verification_emoji_trophy">Pokal</string>
|
||||||
|
<string name="verification_emoji_ball">Ball</string>
|
||||||
|
<string name="verification_emoji_guitar">Gitarre</string>
|
||||||
|
<string name="verification_emoji_trumpet">Trompete</string>
|
||||||
|
<string name="verification_emoji_bell">Glocke</string>
|
||||||
|
<string name="verification_emoji_anchor">Anker</string>
|
||||||
|
<string name="verification_emoji_headphone">Kopfhörer</string>
|
||||||
|
<string name="verification_emoji_folder">Ordner</string>
|
||||||
|
<string name="verification_emoji_pin">Stecknadel</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Sende eine Nachricht…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Sendewarteschlange leeren</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -167,4 +167,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Hasierako sinkronizazioa:
|
<string name="initial_sync_start_importing_account_data">Hasierako sinkronizazioa:
|
||||||
\nKontuaren datuak inportatzen</string>
|
\nKontuaren datuak inportatzen</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s erabiltzaileak gela hau eguneratu du.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Mezua bidaltzen…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Garbitu bidalketa-ilara</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -168,4 +168,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Alkusynkronointi:
|
<string name="initial_sync_start_importing_account_data">Alkusynkronointi:
|
||||||
\nTuodaan tilin tietoja</string>
|
\nTuodaan tilin tietoja</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s päivitti tämän huoneen.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Lähetetään viestiä…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Tyhjennä lähetysjono</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -167,4 +167,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Synchronisation initiale :
|
<string name="initial_sync_start_importing_account_data">Synchronisation initiale :
|
||||||
\nImportation des données du compte</string>
|
\nImportation des données du compte</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s a mis à niveau ce salon.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Envoi du message…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Vider la file d’envoi</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -166,4 +166,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Induló szinkronizáció:
|
<string name="initial_sync_start_importing_account_data">Induló szinkronizáció:
|
||||||
\nFiók adatok betöltése</string>
|
\nFiók adatok betöltése</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s frissítette ezt a szobát.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Üzenet küldése…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Küldő sor ürítése</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -167,4 +167,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Sync iniziale:
|
<string name="initial_sync_start_importing_account_data">Sync iniziale:
|
||||||
\nImportazione dati account</string>
|
\nImportazione dati account</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s ha aggiornato questa stanza.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Invio messaggio in corso …</string>
|
||||||
|
<string name="clear_timeline_send_queue">Cancella la coda di invio</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,6 +1,173 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="summary_message">%1$s: %2$s</string>
|
<string name="summary_message">%1$s: %2$s</string>
|
||||||
<string name="notice_room_invite_no_invitee">%s\'의 초대</string>
|
<string name="notice_room_invite_no_invitee">%s님의 초대</string>
|
||||||
<string name="verification_emoji_headphone">헤드폰</string>
|
<string name="verification_emoji_headphone">헤드폰</string>
|
||||||
|
<string name="summary_user_sent_image">%1$s님이 사진을 보냈습니다.</string>
|
||||||
|
<string name="summary_user_sent_sticker">%1$s님이 스티커를 보냈습니다.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_invite">%1$s님이 %2$s님을 초대했습니다</string>
|
||||||
|
<string name="notice_room_invite_you">%1$s님이 당신을 초대했습니다</string>
|
||||||
|
<string name="notice_room_join">%1$s님이 참가했습니다</string>
|
||||||
|
<string name="notice_room_leave">%1$s님이 떠났습니다</string>
|
||||||
|
<string name="notice_room_reject">%1$s님이 초대를 거부했습니다</string>
|
||||||
|
<string name="notice_room_kick">%1$s님이 %2$s님을 추방했습니다</string>
|
||||||
|
<string name="notice_room_unban">%1$s님이 %2$s님의 차단을 풀었습니다</string>
|
||||||
|
<string name="notice_room_ban">%1$s님이 %2$s님을 차단했습니다</string>
|
||||||
|
<string name="notice_room_withdraw">%1$s님이 %2$s님의 초대를 취소했습니다</string>
|
||||||
|
<string name="notice_avatar_url_changed">%1$s님이 아바타를 변경했습니다</string>
|
||||||
|
<string name="notice_display_name_set">%1$s님이 표시 이름을 %2$s(으)로 설정했습니다</string>
|
||||||
|
<string name="notice_display_name_changed_from">%1$s님이 표시 이름을 %2$s에서 %3$s(으)로 변경했습니다</string>
|
||||||
|
<string name="notice_display_name_removed">%1$s님이 표시 이름을 삭제했습니다 (%2$s)</string>
|
||||||
|
<string name="notice_room_topic_changed">%1$s님이 주제를 다음으로 변경했습니다: %2$s</string>
|
||||||
|
<string name="notice_room_name_changed">%1$s님이 방 이름을 다음으로 변경했습니다: %2$s</string>
|
||||||
|
<string name="notice_placed_video_call">%s님이 영상 통화를 걸었습니다.</string>
|
||||||
|
<string name="notice_placed_voice_call">%s님이 음성 통화를 걸었습니다.</string>
|
||||||
|
<string name="notice_answered_call">%s님이 전화를 받았습니다.</string>
|
||||||
|
<string name="notice_ended_call">%s님이 전화를 끊었습니다.</string>
|
||||||
|
<string name="notice_made_future_room_visibility">%1$s님이 이후 %2$s에게 방 기록을 공개했습니다</string>
|
||||||
|
<string name="notice_room_visibility_invited">초대된 시점부터 모든 방 구성원.</string>
|
||||||
|
<string name="notice_room_visibility_joined">들어온 시점부터 모든 방 구성원.</string>
|
||||||
|
<string name="notice_room_visibility_shared">모든 방 구성원.</string>
|
||||||
|
<string name="notice_room_visibility_world_readable">누구나.</string>
|
||||||
|
<string name="notice_room_visibility_unknown">알 수 없음 (%s).</string>
|
||||||
|
<string name="notice_end_to_end">%1$s님이 종단 간 암호화를 켰습니다 (%2$s)</string>
|
||||||
|
<string name="notice_room_update">%s님이 방을 업그레이드했습니다.</string>
|
||||||
|
|
||||||
|
<string name="notice_requested_voip_conference">%1$s님이 VoIP 회의를 요청했습니다</string>
|
||||||
|
<string name="notice_voip_started">VoIP 회의가 시작했습니다</string>
|
||||||
|
<string name="notice_voip_finished">VoIP 회의가 끝났습니다</string>
|
||||||
|
|
||||||
|
<string name="notice_avatar_changed_too">(아바타도 변경됨)</string>
|
||||||
|
<string name="notice_room_name_removed">%1$s님이 방 이름을 삭제했습니다</string>
|
||||||
|
<string name="notice_room_topic_removed">%1$s님이 방 주제를 삭제했습니다</string>
|
||||||
|
<string name="notice_event_redacted">메시지가 삭제되었습니다</string>
|
||||||
|
<string name="notice_event_redacted_by">메시지가 %1$s님에 의해 삭제되었습니다</string>
|
||||||
|
<string name="notice_event_redacted_with_reason">메시지가 삭제되었습니다 [이유: %1$s]</string>
|
||||||
|
<string name="notice_event_redacted_by_with_reason">메시지가 %1$s님에 의해 삭제되었습니다 [이유: %2$s]</string>
|
||||||
|
<string name="notice_profile_change_redacted">%1$s님이 프로필 %2$s을(를) 업데이트했습니다</string>
|
||||||
|
<string name="notice_room_third_party_invite">%1$s님이 %2$s님에게 방 초대를 보냈습니다</string>
|
||||||
|
<string name="notice_room_third_party_registered_invite">%1$s님이 %2$s의 초대를 수락했습니다</string>
|
||||||
|
|
||||||
|
<string name="notice_crypto_unable_to_decrypt">** 암호를 해독할 수 없음: %s **</string>
|
||||||
|
<string name="notice_crypto_error_unkwown_inbound_session_id">발신인의 기기에서 이 메시지의 키를 보내지 않았습니다.</string>
|
||||||
|
|
||||||
|
<string name="message_reply_to_prefix">이 답장의 질문</string>
|
||||||
|
|
||||||
|
<string name="could_not_redact">검열할 수 없습니다</string>
|
||||||
|
<string name="unable_to_send_message">메시지를 보낼 수 없습니다</string>
|
||||||
|
|
||||||
|
<string name="message_failed_to_upload">사진 업로드에 실패했습니다</string>
|
||||||
|
|
||||||
|
<string name="network_error">네트워크 오류</string>
|
||||||
|
<string name="matrix_error">Matrix 오류</string>
|
||||||
|
|
||||||
|
<string name="room_error_join_failed_empty_room">현재 빈 방에 다시 들어갈 수 없습니다.</string>
|
||||||
|
|
||||||
|
<string name="encrypted_message">암호화된 메시지</string>
|
||||||
|
|
||||||
|
<string name="medium_email">이메일 주소</string>
|
||||||
|
<string name="medium_phone_number">전화번호</string>
|
||||||
|
|
||||||
|
<string name="reply_to_an_image">사진을 보냈습니다.</string>
|
||||||
|
<string name="reply_to_a_video">동영상을 보냈습니다.</string>
|
||||||
|
<string name="reply_to_an_audio_file">오디오 파일을 보냈습니다.</string>
|
||||||
|
<string name="reply_to_a_file">파일을 보냈습니다.</string>
|
||||||
|
|
||||||
|
<string name="room_displayname_invite_from">%s에서 초대함</string>
|
||||||
|
<string name="room_displayname_room_invite">방 초대</string>
|
||||||
|
|
||||||
|
<string name="room_displayname_two_members">%1$s님과 %2$s님</string>
|
||||||
|
|
||||||
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
|
<item quantity="other">%1$s님 외 %2$d명</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<string name="room_displayname_empty_room">빈 방</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="verification_emoji_dog">개</string>
|
||||||
|
<string name="verification_emoji_cat">고양이</string>
|
||||||
|
<string name="verification_emoji_lion">사자</string>
|
||||||
|
<string name="verification_emoji_horse">말</string>
|
||||||
|
<string name="verification_emoji_unicorn">유니콘</string>
|
||||||
|
<string name="verification_emoji_pig">돼지</string>
|
||||||
|
<string name="verification_emoji_elephant">코끼리</string>
|
||||||
|
<string name="verification_emoji_rabbit">토끼</string>
|
||||||
|
<string name="verification_emoji_panda">판다</string>
|
||||||
|
<string name="verification_emoji_rooster">수탉</string>
|
||||||
|
<string name="verification_emoji_penguin">펭귄</string>
|
||||||
|
<string name="verification_emoji_turtle">거북</string>
|
||||||
|
<string name="verification_emoji_fish">물고기</string>
|
||||||
|
<string name="verification_emoji_octopus">문어</string>
|
||||||
|
<string name="verification_emoji_butterfly">나비</string>
|
||||||
|
<string name="verification_emoji_flower">꽃</string>
|
||||||
|
<string name="verification_emoji_tree">나무</string>
|
||||||
|
<string name="verification_emoji_cactus">선인장</string>
|
||||||
|
<string name="verification_emoji_mushroom">버섯</string>
|
||||||
|
<string name="verification_emoji_globe">지구본</string>
|
||||||
|
<string name="verification_emoji_moon">달</string>
|
||||||
|
<string name="verification_emoji_cloud">구름</string>
|
||||||
|
<string name="verification_emoji_fire">불</string>
|
||||||
|
<string name="verification_emoji_banana">바나나</string>
|
||||||
|
<string name="verification_emoji_apple">사과</string>
|
||||||
|
<string name="verification_emoji_strawberry">딸기</string>
|
||||||
|
<string name="verification_emoji_corn">옥수수</string>
|
||||||
|
<string name="verification_emoji_pizza">피자</string>
|
||||||
|
<string name="verification_emoji_cake">케이크</string>
|
||||||
|
<string name="verification_emoji_heart">하트</string>
|
||||||
|
<string name="verification_emoji_smiley">웃음</string>
|
||||||
|
<string name="verification_emoji_robot">로봇</string>
|
||||||
|
<string name="verification_emoji_hat">모자</string>
|
||||||
|
<string name="verification_emoji_glasses">안경</string>
|
||||||
|
<string name="verification_emoji_wrench">스패너</string>
|
||||||
|
<string name="verification_emoji_santa">산타클로스</string>
|
||||||
|
<string name="verification_emoji_thumbsup">좋아요</string>
|
||||||
|
<string name="verification_emoji_umbrella">우산</string>
|
||||||
|
<string name="verification_emoji_hourglass">모래시계</string>
|
||||||
|
<string name="verification_emoji_clock">시계</string>
|
||||||
|
<string name="verification_emoji_gift">선물</string>
|
||||||
|
<string name="verification_emoji_lightbulb">전구</string>
|
||||||
|
<string name="verification_emoji_book">책</string>
|
||||||
|
<string name="verification_emoji_pencil">연필</string>
|
||||||
|
<string name="verification_emoji_paperclip">클립</string>
|
||||||
|
<string name="verification_emoji_scissors">가위</string>
|
||||||
|
<string name="verification_emoji_lock">자물쇠</string>
|
||||||
|
<string name="verification_emoji_key">열쇠</string>
|
||||||
|
<string name="verification_emoji_hammer">망치</string>
|
||||||
|
<string name="verification_emoji_telephone">전화기</string>
|
||||||
|
<string name="verification_emoji_flag">깃발</string>
|
||||||
|
<string name="verification_emoji_train">기차</string>
|
||||||
|
<string name="verification_emoji_bicycle">자전거</string>
|
||||||
|
<string name="verification_emoji_airplane">비행기</string>
|
||||||
|
<string name="verification_emoji_rocket">로켓</string>
|
||||||
|
<string name="verification_emoji_trophy">트로피</string>
|
||||||
|
<string name="verification_emoji_ball">공</string>
|
||||||
|
<string name="verification_emoji_guitar">기타</string>
|
||||||
|
<string name="verification_emoji_trumpet">트럼펫</string>
|
||||||
|
<string name="verification_emoji_bell">종</string>
|
||||||
|
<string name="verification_emoji_anchor">닻</string>
|
||||||
|
<string name="verification_emoji_folder">폴더</string>
|
||||||
|
<string name="verification_emoji_pin">핀</string>
|
||||||
|
|
||||||
|
<string name="initial_sync_start_importing_account">초기 동기화:
|
||||||
|
\n계정 가져오는 중…</string>
|
||||||
|
<string name="initial_sync_start_importing_account_crypto">초기 동기화:
|
||||||
|
\n암호 가져오는 중</string>
|
||||||
|
<string name="initial_sync_start_importing_account_rooms">초기 동기화:
|
||||||
|
\n방 가져오는 중</string>
|
||||||
|
<string name="initial_sync_start_importing_account_joined_rooms">초기 동기화:
|
||||||
|
\n들어간 방 가져오는 중</string>
|
||||||
|
<string name="initial_sync_start_importing_account_invited_rooms">초기 동기화:
|
||||||
|
\n초대받은 방 가져오는 중</string>
|
||||||
|
<string name="initial_sync_start_importing_account_left_rooms">초기 동기화:
|
||||||
|
\n떠난 방 가져오는 중</string>
|
||||||
|
<string name="initial_sync_start_importing_account_groups">초기 동기화:
|
||||||
|
\n커뮤니티 가져오는 중</string>
|
||||||
|
<string name="initial_sync_start_importing_account_data">초기 동기화:
|
||||||
|
\n계정 데이터 가져오는 중</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">메시지 보내는 중…</string>
|
||||||
|
<string name="clear_timeline_send_queue">전송 대기 열 지우기</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -176,4 +176,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Initiële synchronisatie:
|
<string name="initial_sync_start_importing_account_data">Initiële synchronisatie:
|
||||||
\nAccountgegevens worden geïmporteerd</string>
|
\nAccountgegevens worden geïmporteerd</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s heeft dit gesprek opgewaardeerd.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Bericht wordt verstuurd…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Uitgaande wachtrij legen</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<string name="notice_room_invite">%1$s zaprosił(a) %2$s</string>
|
<string name="notice_room_invite">%1$s zaprosił(a) %2$s</string>
|
||||||
<string name="notice_room_invite_you">%1$s zaprosił(a) Cię</string>
|
<string name="notice_room_invite_you">%1$s zaprosił(a) Cię</string>
|
||||||
<string name="notice_room_join">%1$s dołączył(a)</string>
|
<string name="notice_room_join">%1$s dołączył(a)</string>
|
||||||
<string name="notice_room_leave">%1$s wyszedł(-ła)</string>
|
<string name="notice_room_leave">%1$s opuścił(a)</string>
|
||||||
<string name="notice_room_reject">%1$s odrzucił(a) zaproszenie</string>
|
<string name="notice_room_reject">%1$s odrzucił(a) zaproszenie</string>
|
||||||
<string name="notice_room_kick">%1$s wyrzucił(a) %2$s</string>
|
<string name="notice_room_kick">%1$s wyrzucił(a) %2$s</string>
|
||||||
<string name="notice_room_unban">%1$s odblokował(a) %2$s</string>
|
<string name="notice_room_unban">%1$s odblokował(a) %2$s</string>
|
||||||
@ -17,11 +17,11 @@
|
|||||||
<string name="notice_display_name_changed_from">%1$s zmienił(a) wyświetlaną nazwę z %2$s na %3$s</string>
|
<string name="notice_display_name_changed_from">%1$s zmienił(a) wyświetlaną nazwę z %2$s na %3$s</string>
|
||||||
<string name="notice_display_name_removed">%1$s usunął(-ęła) swoją wyświetlaną nazwę (%2$s)</string>
|
<string name="notice_display_name_removed">%1$s usunął(-ęła) swoją wyświetlaną nazwę (%2$s)</string>
|
||||||
<string name="notice_room_topic_changed">%1$s zmienił(a) temat na: %2$s</string>
|
<string name="notice_room_topic_changed">%1$s zmienił(a) temat na: %2$s</string>
|
||||||
<string name="unable_to_send_message">Nie udało się wysłać wiadomości</string>
|
<string name="unable_to_send_message">Nie można wysłać wiadomości</string>
|
||||||
|
|
||||||
<string name="message_failed_to_upload">Nie udało się wysłać zdjęcia</string>
|
<string name="message_failed_to_upload">Przesyłanie zdjęcia nie powiodło się</string>
|
||||||
|
|
||||||
<string name="network_error">ogólne błędy</string>
|
<string name="network_error">Błąd sieci</string>
|
||||||
<string name="matrix_error">Błąd Matrixa</string>
|
<string name="matrix_error">Błąd Matrixa</string>
|
||||||
|
|
||||||
<string name="encrypted_message">Wiadomość zaszyfrowana</string>
|
<string name="encrypted_message">Wiadomość zaszyfrowana</string>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<string name="notice_room_visibility_shared">wszyscy członkowie pokoju.</string>
|
<string name="notice_room_visibility_shared">wszyscy członkowie pokoju.</string>
|
||||||
<string name="notice_room_visibility_world_readable">wszyscy.</string>
|
<string name="notice_room_visibility_world_readable">wszyscy.</string>
|
||||||
<string name="notice_room_name_changed">%1$s zmienił(a) znawę pokoju na: %2$s</string>
|
<string name="notice_room_name_changed">%1$s zmienił(a) nazwę pokoju na: %2$s</string>
|
||||||
<string name="notice_ended_call">%s zakończył(a) rozmowę.</string>
|
<string name="notice_ended_call">%s zakończył(a) rozmowę.</string>
|
||||||
<string name="notice_room_name_removed">%1$s usunął(-ęła) nazwę pokoju</string>
|
<string name="notice_room_name_removed">%1$s usunął(-ęła) nazwę pokoju</string>
|
||||||
<string name="notice_room_topic_removed">%1$s usunął(-ęła) temat pokoju</string>
|
<string name="notice_room_topic_removed">%1$s usunął(-ęła) temat pokoju</string>
|
||||||
@ -57,9 +57,9 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Nie można odszyfrować: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Nie można odszyfrować: %s **</string>
|
||||||
<string name="notice_placed_video_call">%s umieścił wideo rozmowe.</string>
|
<string name="notice_placed_video_call">%s wykonał(a) rozmowę wideo.</string>
|
||||||
<string name="notice_placed_voice_call">%s umieścił połączenie głosowe.</string>
|
<string name="notice_placed_voice_call">%s wykonał(a) połączenie głosowe.</string>
|
||||||
<string name="notice_made_future_room_visibility">%1$s uczynił historię pokoju widoczną do %2$s</string>
|
<string name="notice_made_future_room_visibility">%1$s uczynił(a) przyszłą historię pokoju widoczną dla %2$s</string>
|
||||||
<string name="notice_room_visibility_invited">wszyscy członkowie pokoju, od momentu w którym zostali zaproszeni.</string>
|
<string name="notice_room_visibility_invited">wszyscy członkowie pokoju, od momentu w którym zostali zaproszeni.</string>
|
||||||
<string name="notice_room_visibility_joined">wszyscy członkowie pokoju, od momentu w którym dołączyli.</string>
|
<string name="notice_room_visibility_joined">wszyscy członkowie pokoju, od momentu w którym dołączyli.</string>
|
||||||
<string name="notice_room_visibility_unknown">nieznane (%s).</string>
|
<string name="notice_room_visibility_unknown">nieznane (%s).</string>
|
||||||
@ -147,4 +147,29 @@
|
|||||||
<string name="verification_emoji_santa">Mikołaj</string>
|
<string name="verification_emoji_santa">Mikołaj</string>
|
||||||
<string name="verification_emoji_gift">Prezent</string>
|
<string name="verification_emoji_gift">Prezent</string>
|
||||||
<string name="verification_emoji_hammer">Młotek</string>
|
<string name="verification_emoji_hammer">Młotek</string>
|
||||||
|
<string name="notice_room_update">%s zakutalizował(a) ten pokój.</string>
|
||||||
|
|
||||||
|
<string name="verification_emoji_thumbsup">Kciuk w górę</string>
|
||||||
|
<string name="verification_emoji_lock">Zamek</string>
|
||||||
|
<string name="verification_emoji_ball">Piłka</string>
|
||||||
|
<string name="initial_sync_start_importing_account">Synchronizacja początkowa:
|
||||||
|
\nImportowanie konta…</string>
|
||||||
|
<string name="initial_sync_start_importing_account_crypto">Synchronizacja początkowa:
|
||||||
|
\nImportowanie kryptografii</string>
|
||||||
|
<string name="initial_sync_start_importing_account_rooms">Synchronizacja początkowa:
|
||||||
|
\nImportowanie Pokoi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_joined_rooms">Synchronizacja początkowa:
|
||||||
|
\nImportowanie dołączonych Pokoi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_invited_rooms">Synchronizacja początkowa:
|
||||||
|
\nImportowanie zaproszonych Pokoi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_left_rooms">Synchronizacja początkowa:
|
||||||
|
\nImportowanie opuszczonych Pokoi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_groups">Synchronizacja początkowa:
|
||||||
|
\nImportowanie Społeczności</string>
|
||||||
|
<string name="initial_sync_start_importing_account_data">Synchronizacja początkowa:
|
||||||
|
\nImportowanie danych Konta</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Wysyłanie wiadomości…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Wyczyść kolejkę wysyłania</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -78,4 +78,10 @@
|
|||||||
<string name="room_displayname_empty_room">Sala vazia</string>
|
<string name="room_displayname_empty_room">Sala vazia</string>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="summary_user_sent_sticker">%1$s enviou um sticker.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s fez o upgrade da sala.</string>
|
||||||
|
|
||||||
|
<string name="notice_event_redacted">Mensagem removida</string>
|
||||||
|
<string name="notice_event_redacted_by">Mensagem removida por %1$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -169,9 +169,9 @@
|
|||||||
\nИмпорт криптографии</string>
|
\nИмпорт криптографии</string>
|
||||||
<string name="initial_sync_start_importing_account_rooms">Начальная синхронизация:
|
<string name="initial_sync_start_importing_account_rooms">Начальная синхронизация:
|
||||||
\nИмпорт комнат</string>
|
\nИмпорт комнат</string>
|
||||||
<string name="initial_sync_start_importing_account_joined_rooms">Начальная синхронизация:
|
<string name="initial_sync_start_importing_account_joined_rooms">Синхронизация начата:
|
||||||
\nИмпорт присоединенных комнат</string>
|
\nИмпорт присоединенных комнат</string>
|
||||||
<string name="initial_sync_start_importing_account_invited_rooms">Начальная синхронизация:
|
<string name="initial_sync_start_importing_account_invited_rooms">Синхронизация начата:
|
||||||
\nИмпорт приглашенных комнат</string>
|
\nИмпорт приглашенных комнат</string>
|
||||||
<string name="initial_sync_start_importing_account_left_rooms">Начальная синхронизация:
|
<string name="initial_sync_start_importing_account_left_rooms">Начальная синхронизация:
|
||||||
\nИмпорт покинутых комнат</string>
|
\nИмпорт покинутых комнат</string>
|
||||||
@ -180,4 +180,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">Начальная синхронизация:
|
<string name="initial_sync_start_importing_account_data">Начальная синхронизация:
|
||||||
\nИмпорт данных учетной записи</string>
|
\nИмпорт данных учетной записи</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s обновил эту комнату.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Отправка сообщения…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Очистить очередь отправки</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -82,4 +82,95 @@
|
|||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s aktualizoval túto miestnosť.</string>
|
||||||
|
|
||||||
|
<string name="notice_event_redacted">Správa odstránená</string>
|
||||||
|
<string name="notice_event_redacted_by">Správa odstránená používateľom %1$s</string>
|
||||||
|
<string name="notice_event_redacted_with_reason">Správa odstránená [dôvod: %1$s]</string>
|
||||||
|
<string name="notice_event_redacted_by_with_reason">Správa odstránená používateľom %1$s [dôvod: %2$s]</string>
|
||||||
|
<string name="verification_emoji_dog">Pes</string>
|
||||||
|
<string name="verification_emoji_cat">Mačka</string>
|
||||||
|
<string name="verification_emoji_lion">Lev</string>
|
||||||
|
<string name="verification_emoji_horse">Kôň</string>
|
||||||
|
<string name="verification_emoji_unicorn">Jednorožec</string>
|
||||||
|
<string name="verification_emoji_pig">Prasa</string>
|
||||||
|
<string name="verification_emoji_elephant">Slon</string>
|
||||||
|
<string name="verification_emoji_rabbit">Zajac</string>
|
||||||
|
<string name="verification_emoji_panda">Panda</string>
|
||||||
|
<string name="verification_emoji_rooster">Kohút</string>
|
||||||
|
<string name="verification_emoji_penguin">Tučniak</string>
|
||||||
|
<string name="verification_emoji_turtle">Korytnačka</string>
|
||||||
|
<string name="verification_emoji_fish">Ryba</string>
|
||||||
|
<string name="verification_emoji_octopus">Chobotnica</string>
|
||||||
|
<string name="verification_emoji_butterfly">Motýľ</string>
|
||||||
|
<string name="verification_emoji_flower">Kvetina</string>
|
||||||
|
<string name="verification_emoji_tree">Strom</string>
|
||||||
|
<string name="verification_emoji_cactus">Kaktus</string>
|
||||||
|
<string name="verification_emoji_mushroom">Hríb</string>
|
||||||
|
<string name="verification_emoji_globe">Zemeguľa</string>
|
||||||
|
<string name="verification_emoji_moon">Mesiac</string>
|
||||||
|
<string name="verification_emoji_cloud">Oblak</string>
|
||||||
|
<string name="verification_emoji_fire">Oheň</string>
|
||||||
|
<string name="verification_emoji_banana">Banán</string>
|
||||||
|
<string name="verification_emoji_apple">Jablko</string>
|
||||||
|
<string name="verification_emoji_strawberry">Jahoda</string>
|
||||||
|
<string name="verification_emoji_corn">Kukurica</string>
|
||||||
|
<string name="verification_emoji_pizza">Pizza</string>
|
||||||
|
<string name="verification_emoji_cake">Koláč</string>
|
||||||
|
<string name="verification_emoji_heart">Srdce</string>
|
||||||
|
<string name="verification_emoji_smiley">Úsmev</string>
|
||||||
|
<string name="verification_emoji_robot">Robot</string>
|
||||||
|
<string name="verification_emoji_hat">Klobúk</string>
|
||||||
|
<string name="verification_emoji_glasses">Okuliare</string>
|
||||||
|
<string name="verification_emoji_wrench">Skrutkovač</string>
|
||||||
|
<string name="verification_emoji_santa">Mikuláš</string>
|
||||||
|
<string name="verification_emoji_thumbsup">Palec nahor</string>
|
||||||
|
<string name="verification_emoji_umbrella">Dáždnik</string>
|
||||||
|
<string name="verification_emoji_hourglass">Presýpacie hodiny</string>
|
||||||
|
<string name="verification_emoji_clock">Hodiny</string>
|
||||||
|
<string name="verification_emoji_gift">Darček</string>
|
||||||
|
<string name="verification_emoji_lightbulb">Žiarovka</string>
|
||||||
|
<string name="verification_emoji_book">Kniha</string>
|
||||||
|
<string name="verification_emoji_pencil">Ceruzka</string>
|
||||||
|
<string name="verification_emoji_paperclip">Kancelárska sponka</string>
|
||||||
|
<string name="verification_emoji_scissors">Nožnice</string>
|
||||||
|
<string name="verification_emoji_lock">Zámok</string>
|
||||||
|
<string name="verification_emoji_key">Kľúč</string>
|
||||||
|
<string name="verification_emoji_hammer">Kladivo</string>
|
||||||
|
<string name="verification_emoji_telephone">Telefón</string>
|
||||||
|
<string name="verification_emoji_flag">Vlajka</string>
|
||||||
|
<string name="verification_emoji_train">Vlak</string>
|
||||||
|
<string name="verification_emoji_bicycle">Bicykel</string>
|
||||||
|
<string name="verification_emoji_airplane">Lietadlo</string>
|
||||||
|
<string name="verification_emoji_rocket">Raketa</string>
|
||||||
|
<string name="verification_emoji_trophy">Trofej</string>
|
||||||
|
<string name="verification_emoji_ball">Lopta</string>
|
||||||
|
<string name="verification_emoji_guitar">Gitara</string>
|
||||||
|
<string name="verification_emoji_trumpet">Trúbka</string>
|
||||||
|
<string name="verification_emoji_bell">Zvonček</string>
|
||||||
|
<string name="verification_emoji_anchor">Kotva</string>
|
||||||
|
<string name="verification_emoji_headphone">Schlúchadlá</string>
|
||||||
|
<string name="verification_emoji_folder">Priečinok</string>
|
||||||
|
<string name="verification_emoji_pin">Pin</string>
|
||||||
|
|
||||||
|
<string name="initial_sync_start_importing_account">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import účtu…</string>
|
||||||
|
<string name="initial_sync_start_importing_account_crypto">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import šifrovacích kľúčov</string>
|
||||||
|
<string name="initial_sync_start_importing_account_rooms">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import miestností</string>
|
||||||
|
<string name="initial_sync_start_importing_account_joined_rooms">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import miestností, do ktorých ste vstúpili</string>
|
||||||
|
<string name="initial_sync_start_importing_account_invited_rooms">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import pozvánok</string>
|
||||||
|
<string name="initial_sync_start_importing_account_left_rooms">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import opustených miestností</string>
|
||||||
|
<string name="initial_sync_start_importing_account_groups">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import komunít</string>
|
||||||
|
<string name="initial_sync_start_importing_account_data">Úvodná synchronizácia:
|
||||||
|
\nPrebieha import údajov účtu</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Odosielanie správy…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Vymazať správy na odoslanie</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -146,4 +146,26 @@
|
|||||||
<string name="verification_emoji_anchor">Spirancë</string>
|
<string name="verification_emoji_anchor">Spirancë</string>
|
||||||
<string name="verification_emoji_headphone">Kufje</string>
|
<string name="verification_emoji_headphone">Kufje</string>
|
||||||
<string name="verification_emoji_folder">Dosje</string>
|
<string name="verification_emoji_folder">Dosje</string>
|
||||||
|
<string name="notice_room_update">%s e përmirësoi këtë dhomë.</string>
|
||||||
|
|
||||||
|
<string name="initial_sync_start_importing_account">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohet llogaria…</string>
|
||||||
|
<string name="initial_sync_start_importing_account_crypto">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohet kriptografi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_rooms">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohen Dhoma</string>
|
||||||
|
<string name="initial_sync_start_importing_account_joined_rooms">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohen Dhoma Ku Është Bërë Hyrje</string>
|
||||||
|
<string name="initial_sync_start_importing_account_invited_rooms">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohen Dhoma Me Ftesë</string>
|
||||||
|
<string name="initial_sync_start_importing_account_left_rooms">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohen Dhoma të Braktisura</string>
|
||||||
|
<string name="initial_sync_start_importing_account_groups">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohen Bashkësi</string>
|
||||||
|
<string name="initial_sync_start_importing_account_data">Njëkohësimi Fillestar:
|
||||||
|
\nPo importohet të Dhëna Llogarie</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Po dërgohet mesazh…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Spastro radhë pritjeje</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
<string name="notice_room_third_party_registered_invite">%1$s èt d’uutnodigienge vo %2$s anveird</string>
|
<string name="notice_room_third_party_registered_invite">%1$s èt d’uutnodigienge vo %2$s anveird</string>
|
||||||
|
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Kun nie ountsleuteln: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Kun nie ountsleuteln: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">’t Toestel van den afzender èt geen sleutels vo dit bericht gesteurd.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">’t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Als antwoord ip</string>
|
<string name="message_reply_to_prefix">Als antwoord ip</string>
|
||||||
|
|
||||||
@ -150,21 +150,26 @@
|
|||||||
<string name="verification_emoji_folder">Mappe</string>
|
<string name="verification_emoji_folder">Mappe</string>
|
||||||
<string name="verification_emoji_pin">Pinne</string>
|
<string name="verification_emoji_pin">Pinne</string>
|
||||||
|
|
||||||
<string name="initial_sync_start_importing_account">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account">Initiële synchronisoasje:
|
||||||
\nAccount wor geïmporteerd…</string>
|
\nAccount wor geïmporteerd…</string>
|
||||||
<string name="initial_sync_start_importing_account_crypto">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_crypto">Initiële synchronisoasje:
|
||||||
\nCrypto wor geïmporteerd</string>
|
\nCrypto wor geïmporteerd</string>
|
||||||
<string name="initial_sync_start_importing_account_rooms">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_rooms">Initiële synchronisoasje:
|
||||||
\nGesprekkn wordn geïmporteerd</string>
|
\nGesprekkn wordn geïmporteerd</string>
|
||||||
<string name="initial_sync_start_importing_account_joined_rooms">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_joined_rooms">Initiële synchronisoasje:
|
||||||
\nDeelgenoomn gesprekken wordn geïmporteerd</string>
|
\nDeelgenoomn gesprekken wordn geïmporteerd</string>
|
||||||
<string name="initial_sync_start_importing_account_invited_rooms">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_invited_rooms">Initiële synchronisoasje:
|
||||||
\nUutgenodigde gesprekkn wordn geïmporteerd</string>
|
\nUutgenodigde gesprekkn wordn geïmporteerd</string>
|
||||||
<string name="initial_sync_start_importing_account_left_rooms">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_left_rooms">Initiële synchronisoasje:
|
||||||
\nVerloatn gesprekkn wordn geïmporteerd</string>
|
\nVerloatn gesprekkn wordn geïmporteerd</string>
|
||||||
<string name="initial_sync_start_importing_account_groups">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_groups">Initiële synchronisoasje:
|
||||||
\nGemeenschappn wordn geïmporteerd</string>
|
\nGemeenschappn wordn geïmporteerd</string>
|
||||||
<string name="initial_sync_start_importing_account_data">Initiële synchronisoatie:
|
<string name="initial_sync_start_importing_account_data">Initiële synchronisoasje:
|
||||||
\nAccountgegeevns wordn geïmporteerd</string>
|
\nAccountgegeevns wordn geïmporteerd</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s èt da gesprek hier ipgewoardeerd.</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">Bericht wor verstuurd…</string>
|
||||||
|
<string name="clear_timeline_send_queue">Uutgoande wachtreeke leegn</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -162,4 +162,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">初始化同步:
|
<string name="initial_sync_start_importing_account_data">初始化同步:
|
||||||
\n正在导入账号数据</string>
|
\n正在导入账号数据</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s 升级了聊天室。</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">正在发送消息…</string>
|
||||||
|
<string name="clear_timeline_send_queue">清除正在发送队列</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -165,4 +165,9 @@
|
|||||||
<string name="initial_sync_start_importing_account_data">初始化同步:
|
<string name="initial_sync_start_importing_account_data">初始化同步:
|
||||||
\n正在匯入帳號資料</string>
|
\n正在匯入帳號資料</string>
|
||||||
|
|
||||||
|
<string name="notice_room_update">%s 已升級此聊天室。</string>
|
||||||
|
|
||||||
|
<string name="event_status_sending_message">正在傳送訊息……</string>
|
||||||
|
<string name="clear_timeline_send_queue">清除傳送佇列</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -15,7 +15,7 @@ androidExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 3
|
ext.versionMinor = 5
|
||||||
ext.versionPatch = 0
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
@ -29,7 +29,15 @@ static def generateVersionCodeFromTimestamp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generateVersionCodeFromVersionName() {
|
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() {
|
static def gitRevision() {
|
||||||
@ -43,10 +51,18 @@ static def gitRevisionDate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static def gitBranchName() {
|
static def gitBranchName() {
|
||||||
def cmd = "git name-rev --name-only HEAD"
|
def cmd = "git rev-parse --abbrev-ref HEAD"
|
||||||
return cmd.execute().text.trim()
|
return cmd.execute().text.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static def getVersionSuffix() {
|
||||||
|
if (gitBranchName() == "master") {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return "-dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project.android.buildTypes.all { buildType ->
|
project.android.buildTypes.all { buildType ->
|
||||||
buildType.javaCompileOptions.annotationProcessorOptions.arguments =
|
buildType.javaCompileOptions.annotationProcessorOptions.arguments =
|
||||||
[
|
[
|
||||||
@ -71,11 +87,11 @@ android {
|
|||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
// For release, use generateVersionCodeFromVersionName()
|
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.
|
||||||
versionCode generateVersionCodeFromTimestamp()
|
// Other branches (master, features, etc.) will have version code based on application version.
|
||||||
//versionCode generateVersionCodeFromVersionName()
|
versionCode project.getVersionCode()
|
||||||
|
|
||||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}-dev"
|
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getVersionSuffix()}"
|
||||||
|
|
||||||
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
|
||||||
resValue "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 ->
|
variant.outputs.each { output ->
|
||||||
def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI))
|
def baseAbiVersionCode = project.ext.abiVersionCodes.get(output.getFilter(OutputFile.ABI))
|
||||||
|
// Known limitation: it does not modify the value in the BuildConfig.java generated file
|
||||||
output.versionCodeOverride = baseAbiVersionCode * 10_000_000 + variant.versionCode
|
output.versionCodeOverride = baseAbiVersionCode * 10_000_000 + variant.versionCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,6 +318,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'diff_match_patch:diff_match_patch:current'
|
implementation 'diff_match_patch:diff_match_patch:current'
|
||||||
|
|
||||||
|
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.riotx.fdroid.features.settings.troubleshoot
|
package im.vector.riotx.fdroid.features.settings.troubleshoot
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
@ -25,12 +24,12 @@ import javax.inject.Inject
|
|||||||
/**
|
/**
|
||||||
* Test that the application is started on boot
|
* Test that the application is started on boot
|
||||||
*/
|
*/
|
||||||
class TestAutoStartBoot @Inject constructor(private val context: AppCompatActivity,
|
class TestAutoStartBoot @Inject constructor(private val vectorPreferences: VectorPreferences,
|
||||||
private val stringProvider: StringProvider)
|
private val stringProvider: StringProvider)
|
||||||
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
|
: TroubleshootTest(R.string.settings_troubleshoot_test_service_boot_title) {
|
||||||
|
|
||||||
override fun perform() {
|
override fun perform() {
|
||||||
if (VectorPreferences.autoStartOnBoot(context)) {
|
if (vectorPreferences.autoStartOnBoot()) {
|
||||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_success)
|
||||||
status = TestStatus.SUCCESS
|
status = TestStatus.SUCCESS
|
||||||
quickFix = null
|
quickFix = null
|
||||||
@ -38,7 +37,7 @@ class TestAutoStartBoot @Inject constructor(private val context: AppCompatActivi
|
|||||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_failed)
|
description = stringProvider.getString(R.string.settings_troubleshoot_test_service_boot_failed)
|
||||||
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
|
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_service_boot_quickfix) {
|
||||||
override fun doFix() {
|
override fun doFix() {
|
||||||
VectorPreferences.setAutoStartOnBoot(context, true)
|
vectorPreferences.setAutoStartOnBoot(true)
|
||||||
manager?.retry()
|
manager?.retry()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,9 @@ object FcmHelper {
|
|||||||
AlarmSyncBroadcastReceiver.cancelAlarm(context)
|
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
|
//We need to use alarm in this mode
|
||||||
if (VectorPreferences.areNotificationEnabledForDevice(context) && activeSessionHolder.hasActiveSession()) {
|
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||||
val currentSession = activeSessionHolder.getActiveSession()
|
val currentSession = activeSessionHolder.getActiveSession()
|
||||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L)
|
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.myUserId, 4_000L)
|
||||||
Timber.i("Alarm scheduled to restart service")
|
Timber.i("Alarm scheduled to restart service")
|
||||||
|
@ -52,6 +52,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
private lateinit var notifiableEventResolver: NotifiableEventResolver
|
private lateinit var notifiableEventResolver: NotifiableEventResolver
|
||||||
private lateinit var pusherManager: PushersManager
|
private lateinit var pusherManager: PushersManager
|
||||||
private lateinit var activeSessionHolder: ActiveSessionHolder
|
private lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
|
private lateinit var vectorPreferences: VectorPreferences
|
||||||
|
|
||||||
// UI handler
|
// UI handler
|
||||||
private val mUIHandler by lazy {
|
private val mUIHandler by lazy {
|
||||||
@ -64,6 +65,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
notifiableEventResolver = vectorComponent().notifiableEventResolver()
|
notifiableEventResolver = vectorComponent().notifiableEventResolver()
|
||||||
pusherManager = vectorComponent().pusherManager()
|
pusherManager = vectorComponent().pusherManager()
|
||||||
activeSessionHolder = vectorComponent().activeSessionHolder()
|
activeSessionHolder = vectorComponent().activeSessionHolder()
|
||||||
|
vectorPreferences = vectorComponent().vectorPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +74,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
* @param message the message
|
* @param message the message
|
||||||
*/
|
*/
|
||||||
override fun onMessageReceived(message: RemoteMessage?) {
|
override fun onMessageReceived(message: RemoteMessage?) {
|
||||||
if (!VectorPreferences.areNotificationEnabledForDevice(applicationContext)) {
|
if (!vectorPreferences.areNotificationEnabledForDevice()) {
|
||||||
Timber.i("Notification are disabled for this device")
|
Timber.i("Notification are disabled for this device")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -107,7 +109,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||||||
if (refreshedToken == null) {
|
if (refreshedToken == null) {
|
||||||
Timber.w("onNewToken:received null token")
|
Timber.w("onNewToken:received null token")
|
||||||
} else {
|
} else {
|
||||||
if (VectorPreferences.areNotificationEnabledForDevice(applicationContext) && activeSessionHolder.hasActiveSession()) {
|
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||||
pusherManager.registerPusherWithFcmKey(refreshedToken)
|
pusherManager.registerPusherWithFcmKey(refreshedToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import com.google.firebase.iid.FirebaseInstanceId
|
|||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.pushers.PushersManager
|
import im.vector.riotx.core.pushers.PushersManager
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,7 +106,7 @@ object FcmHelper {
|
|||||||
// No op
|
// No op
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEnterBackground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||||
// TODO FCM fallback
|
// TODO FCM fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
vector/src/main/java/im/vector/riotx/EmojiCompatWrapper.kt
Normal file
67
vector/src/main/java/im/vector/riotx/EmojiCompatWrapper.kt
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.provider.FontRequest
|
||||||
|
import androidx.emoji.text.EmojiCompat
|
||||||
|
import androidx.emoji.text.FontRequestEmojiCompatConfig
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class EmojiCompatWrapper @Inject constructor(private val context: Context) {
|
||||||
|
|
||||||
|
private var initialized = false
|
||||||
|
|
||||||
|
fun init(fontRequest: FontRequest) {
|
||||||
|
|
||||||
|
//Use emoji compat for the benefit of emoji spans
|
||||||
|
val config = FontRequestEmojiCompatConfig(context, fontRequest)
|
||||||
|
// we want to replace all emojis with selected font
|
||||||
|
.setReplaceAll(true)
|
||||||
|
//Debug options
|
||||||
|
// .setEmojiSpanIndicatorEnabled(true)
|
||||||
|
// .setEmojiSpanIndicatorColor(Color.GREEN)
|
||||||
|
EmojiCompat.init(config)
|
||||||
|
.registerInitCallback(object : EmojiCompat.InitCallback() {
|
||||||
|
override fun onInitialized() {
|
||||||
|
Timber.v("Emoji compat onInitialized success ")
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailed(throwable: Throwable?) {
|
||||||
|
Timber.e(throwable, "Failed to init EmojiCompat")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun safeEmojiSpanify(sequence: CharSequence): CharSequence {
|
||||||
|
if (initialized) {
|
||||||
|
try {
|
||||||
|
return EmojiCompat.get().process(sequence)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
//Defensive coding against error (should not happend as it is initialized)
|
||||||
|
Timber.e(throwable, "Failed to init EmojiCompat")
|
||||||
|
return sequence
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return sequence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,7 @@ import im.vector.riotx.core.di.DaggerVectorComponent
|
|||||||
import im.vector.riotx.core.di.HasVectorInjector
|
import im.vector.riotx.core.di.HasVectorInjector
|
||||||
import im.vector.riotx.core.di.VectorComponent
|
import im.vector.riotx.core.di.VectorComponent
|
||||||
import im.vector.riotx.core.extensions.configureAndStart
|
import im.vector.riotx.core.extensions.configureAndStart
|
||||||
|
import im.vector.riotx.core.utils.initKnownEmojiHashSet
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
@ -49,12 +50,12 @@ import im.vector.riotx.features.notifications.NotificationUtils
|
|||||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
import im.vector.riotx.features.rageshake.VectorFileLogger
|
import im.vector.riotx.features.rageshake.VectorFileLogger
|
||||||
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
||||||
import im.vector.riotx.features.version.getVersion
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
|
import im.vector.riotx.features.version.VersionProvider
|
||||||
import im.vector.riotx.push.fcm.FcmHelper
|
import im.vector.riotx.push.fcm.FcmHelper
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import im.vector.riotx.core.utils.initKnownEmojiHashSet
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
||||||
@ -65,10 +66,13 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
|||||||
@Inject lateinit var authenticator: Authenticator
|
@Inject lateinit var authenticator: Authenticator
|
||||||
@Inject lateinit var vectorConfiguration: VectorConfiguration
|
@Inject lateinit var vectorConfiguration: VectorConfiguration
|
||||||
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
|
@Inject lateinit var emojiCompatFontProvider: EmojiCompatFontProvider
|
||||||
|
@Inject lateinit var emojiCompatWrapper: EmojiCompatWrapper
|
||||||
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
@Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
|
||||||
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener
|
@Inject lateinit var pushRuleTriggerListener: PushRuleTriggerListener
|
||||||
|
@Inject lateinit var vectorPreferences: VectorPreferences
|
||||||
|
@Inject lateinit var versionProvider: VersionProvider
|
||||||
lateinit var vectorComponent: VectorComponent
|
lateinit var vectorComponent: VectorComponent
|
||||||
private var fontThreadHandler: Handler? = null
|
private var fontThreadHandler: Handler? = null
|
||||||
|
|
||||||
@ -82,9 +86,12 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
|||||||
vectorComponent = DaggerVectorComponent.factory().create(this)
|
vectorComponent = DaggerVectorComponent.factory().create(this)
|
||||||
vectorComponent.inject(this)
|
vectorComponent.inject(this)
|
||||||
vectorUncaughtExceptionHandler.activate(this)
|
vectorUncaughtExceptionHandler.activate(this)
|
||||||
// Log
|
|
||||||
VectorFileLogger.init(this)
|
if (BuildConfig.DEBUG) {
|
||||||
Timber.plant(Timber.DebugTree(), VectorFileLogger)
|
Timber.plant(Timber.DebugTree())
|
||||||
|
}
|
||||||
|
Timber.plant(vectorComponent.vectorFileLogger())
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Stetho.initializeWithDefaults(this)
|
Stetho.initializeWithDefaults(this)
|
||||||
}
|
}
|
||||||
@ -102,6 +109,9 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
|||||||
)
|
)
|
||||||
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
FontsContractCompat.requestFont(this, fontRequest, emojiCompatFontProvider, getFontThreadHandler())
|
||||||
vectorConfiguration.initConfiguration()
|
vectorConfiguration.initConfiguration()
|
||||||
|
|
||||||
|
emojiCompatWrapper.init(fontRequest)
|
||||||
|
|
||||||
NotificationUtils.createNotificationChannels(applicationContext)
|
NotificationUtils.createNotificationChannels(applicationContext)
|
||||||
if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
|
if (authenticator.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
|
||||||
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!
|
val lastAuthenticatedSession = authenticator.getLastAuthenticatedSession()!!
|
||||||
@ -122,7 +132,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
|||||||
fun entersBackground() {
|
fun entersBackground() {
|
||||||
Timber.i("App entered background") // call persistInfo
|
Timber.i("App entered background") // call persistInfo
|
||||||
notificationDrawerManager.persistInfo()
|
notificationDrawerManager.persistInfo()
|
||||||
FcmHelper.onEnterBackground(appContext, activeSessionHolder)
|
FcmHelper.onEnterBackground(appContext, vectorPreferences, activeSessionHolder)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//This should be done as early as possible
|
//This should be done as early as possible
|
||||||
@ -138,7 +148,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun logInfo() {
|
private fun logInfo() {
|
||||||
val appVersion = getVersion(longFormat = true, useBuildNumber = true)
|
val appVersion = versionProvider.getVersion(longFormat = true, useBuildNumber = true)
|
||||||
val sdkVersion = Matrix.getSdkVersion()
|
val sdkVersion = Matrix.getSdkVersion()
|
||||||
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
|
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
|
||||||
|
|
||||||
|
@ -14,15 +14,18 @@
|
|||||||
* limitations under the License.
|
* 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 im.vector.riotx.core.resources.LocaleProvider
|
||||||
import org.threeten.bp.LocalDateTime
|
import org.threeten.bp.LocalDateTime
|
||||||
import org.threeten.bp.format.DateTimeFormatter
|
import org.threeten.bp.format.DateTimeFormatter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
class TimelineDateFormatter @Inject constructor (private val localeProvider: LocaleProvider) {
|
class VectorDateFormatter @Inject constructor(private val context: Context,
|
||||||
|
private val localeProvider: LocaleProvider) {
|
||||||
|
|
||||||
private val messageHourFormatter by lazy {
|
private val messageHourFormatter by lazy {
|
||||||
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
||||||
@ -39,4 +42,16 @@ class TimelineDateFormatter @Inject constructor (private val localeProvider: Loc
|
|||||||
return messageDayFormatter.format(localDateTime)
|
return messageDayFormatter.format(localDateTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatRelativeDateTime(time: Long?): String {
|
||||||
|
if (time == null) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return DateUtils.getRelativeDateTimeString(context,
|
||||||
|
time,
|
||||||
|
DateUtils.DAY_IN_MILLIS,
|
||||||
|
2 * DateUtils.DAY_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_SHOW_WEEKDAY
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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.createdirect.CreateDirectRoomKnownUsersFragment
|
||||||
import im.vector.riotx.features.home.group.GroupListFragment
|
import im.vector.riotx.features.home.group.GroupListFragment
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
import im.vector.riotx.features.home.room.detail.RoomDetailFragment
|
||||||
import im.vector.riotx.features.home.room.detail.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.filtered.FilteredRoomsActivity
|
||||||
import im.vector.riotx.features.home.room.list.RoomListFragment
|
import im.vector.riotx.features.home.room.list.RoomListFragment
|
||||||
import im.vector.riotx.features.invite.VectorInviteView
|
import im.vector.riotx.features.invite.VectorInviteView
|
||||||
@ -53,6 +58,7 @@ import im.vector.riotx.features.rageshake.BugReportActivity
|
|||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.RageShake
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
||||||
|
import im.vector.riotx.features.reactions.widget.ReactionButton
|
||||||
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
import im.vector.riotx.features.roomdirectory.PublicRoomsFragment
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
@ -60,12 +66,15 @@ import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment
|
|||||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsAdvancedNotificationPreferenceFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsHelpAboutFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
|
import im.vector.riotx.features.settings.VectorSettingsNotificationPreferenceFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
import im.vector.riotx.features.settings.VectorSettingsNotificationsTroubleshootFragment
|
||||||
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
|
||||||
|
import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||||
|
|
||||||
@Component(dependencies = [VectorComponent::class], modules = [ViewModelModule::class, HomeModule::class])
|
@Component(dependencies = [VectorComponent::class], modules = [AssistedInjectModule::class, ViewModelModule::class, HomeModule::class])
|
||||||
@ScreenScope
|
@ScreenScope
|
||||||
interface ScreenComponent {
|
interface ScreenComponent {
|
||||||
|
|
||||||
@ -153,6 +162,12 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)
|
fun inject(vectorSettingsPreferencesFragment: VectorSettingsPreferencesFragment)
|
||||||
|
|
||||||
|
fun inject(vectorSettingsAdvancedNotificationPreferenceFragment: VectorSettingsAdvancedNotificationPreferenceFragment)
|
||||||
|
|
||||||
|
fun inject(vectorSettingsSecurityPrivacyFragment: VectorSettingsSecurityPrivacyFragment)
|
||||||
|
|
||||||
|
fun inject(vectorSettingsHelpAboutFragment: VectorSettingsHelpAboutFragment)
|
||||||
|
|
||||||
fun inject(userAvatarPreference: UserAvatarPreference)
|
fun inject(userAvatarPreference: UserAvatarPreference)
|
||||||
|
|
||||||
fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment)
|
fun inject(vectorSettingsNotificationsTroubleshootFragment: VectorSettingsNotificationsTroubleshootFragment)
|
||||||
@ -165,6 +180,10 @@ interface ScreenComponent {
|
|||||||
|
|
||||||
fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
|
fun inject(createDirectRoomActivity: CreateDirectRoomActivity)
|
||||||
|
|
||||||
|
fun inject(displayReadReceiptsBottomSheet: DisplayReadReceiptsBottomSheet)
|
||||||
|
|
||||||
|
fun inject(reactionButton: ReactionButton)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(vectorComponent: VectorComponent,
|
fun create(vectorComponent: VectorComponent,
|
||||||
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.api.Matrix
|
|||||||
import im.vector.matrix.android.api.auth.Authenticator
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.riotx.EmojiCompatFontProvider
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
|
import im.vector.riotx.EmojiCompatWrapper
|
||||||
import im.vector.riotx.VectorApplication
|
import im.vector.riotx.VectorApplication
|
||||||
import im.vector.riotx.core.pushers.PushersManager
|
import im.vector.riotx.core.pushers.PushersManager
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
@ -40,7 +41,9 @@ import im.vector.riotx.features.notifications.NotificationBroadcastReceiver
|
|||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
|
import im.vector.riotx.features.rageshake.VectorFileLogger
|
||||||
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
|
||||||
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Component(modules = [VectorModule::class])
|
@Component(modules = [VectorModule::class])
|
||||||
@ -69,6 +72,8 @@ interface VectorComponent {
|
|||||||
|
|
||||||
fun emojiCompatFontProvider(): EmojiCompatFontProvider
|
fun emojiCompatFontProvider(): EmojiCompatFontProvider
|
||||||
|
|
||||||
|
fun emojiCompatWrapper() : EmojiCompatWrapper
|
||||||
|
|
||||||
fun eventHtmlRenderer(): EventHtmlRenderer
|
fun eventHtmlRenderer(): EventHtmlRenderer
|
||||||
|
|
||||||
fun navigator(): Navigator
|
fun navigator(): Navigator
|
||||||
@ -95,6 +100,10 @@ interface VectorComponent {
|
|||||||
|
|
||||||
fun notifiableEventResolver(): NotifiableEventResolver
|
fun notifiableEventResolver(): NotifiableEventResolver
|
||||||
|
|
||||||
|
fun vectorPreferences(): VectorPreferences
|
||||||
|
|
||||||
|
fun vectorFileLogger(): VectorFileLogger
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance context: Context): VectorComponent
|
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.KeysBackupRestoreFromKeyViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel
|
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
|
||||||
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
|
||||||
import im.vector.riotx.features.home.*
|
import im.vector.riotx.features.home.HomeNavigationViewModel
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
|
import im.vector.riotx.features.home.createdirect.CreateDirectRoomNavigationViewModel
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel
|
|
||||||
import im.vector.riotx.features.home.createdirect.CreateDirectRoomViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.home.group.GroupListViewModel
|
|
||||||
import im.vector.riotx.features.home.group.GroupListViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
|
|
||||||
import im.vector.riotx.features.home.room.detail.RoomDetailViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
|
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.action.*
|
|
||||||
import im.vector.riotx.features.home.room.list.RoomListViewModel
|
|
||||||
import im.vector.riotx.features.home.room.list.RoomListViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
import im.vector.riotx.features.reactions.EmojiChooserViewModel
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel
|
import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryViewModel
|
|
||||||
import im.vector.riotx.features.roomdirectory.RoomDirectoryViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel
|
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerViewModel
|
|
||||||
import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewViewModel
|
|
||||||
import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysViewModel
|
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysViewModel_AssistedFactory
|
|
||||||
import im.vector.riotx.features.workers.signout.SignOutViewModel
|
import im.vector.riotx.features.workers.signout.SignOutViewModel
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
interface ViewModelModule {
|
interface ViewModelModule {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ViewModels with @IntoMap will be injected by this factory
|
* ViewModels with @IntoMap will be injected by this factory
|
||||||
*/
|
*/
|
||||||
@ -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.
|
* Below are bindings for the androidx view models (which extend ViewModel). Will be converted to MvRx ViewModel in the future.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(SignOutViewModel::class)
|
@ViewModelKey(SignOutViewModel::class)
|
||||||
@ -124,62 +101,4 @@ interface ViewModelModule {
|
|||||||
@ViewModelKey(CreateDirectRoomNavigationViewModel::class)
|
@ViewModelKey(CreateDirectRoomNavigationViewModel::class)
|
||||||
fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel
|
fun bindCreateDirectRoomNavigationViewModel(viewModel: CreateDirectRoomNavigationViewModel): ViewModel
|
||||||
|
|
||||||
/**
|
|
||||||
* Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindHomeActivityViewModelFactory(factory: HomeActivityViewModel_AssistedFactory): HomeActivityViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindTextComposerViewModelFactory(factory: TextComposerViewModel_AssistedFactory): TextComposerViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindRoomDetailViewModelFactory(factory: RoomDetailViewModel_AssistedFactory): RoomDetailViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindQuickReactionViewModelFactory(factory: QuickReactionViewModel_AssistedFactory): QuickReactionViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindMessageActionsViewModelFactory(factory: MessageActionsViewModel_AssistedFactory): MessageActionsViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindMessageMenuViewModelFactory(factory: MessageMenuViewModel_AssistedFactory): MessageMenuViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindRoomListViewModelFactory(factory: RoomListViewModel_AssistedFactory): RoomListViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindGroupListViewModelFactory(factory: GroupListViewModel_AssistedFactory): GroupListViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindHomeDetailViewModelFactory(factory: HomeDetailViewModel_AssistedFactory): HomeDetailViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindKeysBackupSettingsViewModelFactory(factory: KeysBackupSettingsViewModel_AssistedFactory): KeysBackupSettingsViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindRoomDirectoryPickerViewModelFactory(factory: RoomDirectoryPickerViewModel_AssistedFactory): RoomDirectoryPickerViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindRoomDirectoryViewModelFactory(factory: RoomDirectoryViewModel_AssistedFactory): RoomDirectoryViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindRoomPreviewViewModelFactory(factory: RoomPreviewViewModel_AssistedFactory): RoomPreviewViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindViewReactionViewModelFactory(factory: ViewReactionViewModel_AssistedFactory): ViewReactionViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindViewEditHistoryViewModelFactory(factory: ViewEditHistoryViewModel_AssistedFactory): ViewEditHistoryViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindCreateDirectRoomViewModelFactory(factory: CreateDirectRoomViewModel_AssistedFactory): CreateDirectRoomViewModel.Factory
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
fun bindPushGatewaysViewModelFactory(factory: PushGatewaysViewModel_AssistedFactory): PushGatewaysViewModel.Factory
|
|
||||||
|
|
||||||
}
|
}
|
@ -161,7 +161,6 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
unBinder?.unbind()
|
unBinder?.unbind()
|
||||||
unBinder = null
|
unBinder = null
|
||||||
|
|
||||||
|
@ -16,13 +16,17 @@
|
|||||||
|
|
||||||
package im.vector.riotx.core.resources
|
package im.vector.riotx.core.resources
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class UserPreferencesProvider @Inject constructor(private val context: Context) {
|
class UserPreferencesProvider @Inject constructor(private val vectorPreferences: VectorPreferences) {
|
||||||
|
|
||||||
fun shouldShowHiddenEvents(): Boolean {
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user