Compare commits

...

94 Commits

Author SHA1 Message Date
Benoit Marty f2c8d4ad02
Merge pull request #549 from vector-im/feature/third_party_invite
Fix rendering issue of accepted third party invitation event
2019-09-06 16:36:30 +02:00
Benoit Marty be524472ec
Merge pull request #546 from vector-im/feature/cleanup
Cleanup
2019-09-06 16:25:08 +02:00
Benoit Marty 1b82a1a24d Cleanup 2019-09-06 15:52:29 +02:00
Benoit Marty cf0b331c3b Handle invite to the current user rendering 2019-09-06 15:48:42 +02:00
Benoit Marty 2a92a3dc80 Fix rendering issue of accepted third party invitation event 2019-09-06 14:34:52 +02:00
Benoit Marty 012840abba Progress in initial sync dialog is decreasing for a step and should not (#532) 2019-09-05 18:14:05 +02:00
Benoit Marty a5975a099e Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:23:09 +02:00
Benoit Marty 38da4b9ee5 Cleanup and document DefaultInitialSyncProgressService 2019-09-05 17:02:03 +02:00
Benoit Marty 242e60fcaa Rename CryptoManager to DefaultCryptoService 2019-09-05 16:14:34 +02:00
Benoit Marty a23be05cbf Better type 2019-09-05 16:04:41 +02:00
Benoit Marty ed39b02924 Avoid using keyword for variable names 2019-09-05 16:04:41 +02:00
Benoit Marty fe931b5361
Merge pull request #418 from Dominaezzz/kotlinify-1
Some more kotlinification
2019-09-05 16:02:30 +02:00
Benoit Marty 90d9cd0587
Merge pull request #416 from Dominaezzz/kt-remove_java_util
Remove most usages of the java.util package
2019-09-05 15:33:03 +02:00
Benoit Marty 9cedb18921
Merge pull request #538 from vector-im/feature/log_mgmt
Reduce release build log level
2019-09-05 15:24:04 +02:00
Benoit Marty e89ba7b87b Update wording 2019-09-05 15:23:38 +02:00
Benoit Marty 902657c22a
Merge pull request #537 from vector-im/feature/fix_crash
Fix crash due to missing informationData (#535)
2019-09-02 15:31:28 +02:00
Valere eec2abf164 Reduce release build log level 2019-09-02 14:33:53 +02:00
Benoit Marty 6879cc8ca8 Fix crash due to missing informationData (#535) 2019-09-02 14:24:36 +02:00
Benoit Marty fd6bbbd3b5 Fix issue with version name (Fixes #533) 2019-08-30 15:57:39 +02:00
Benoit Marty 0ff0b014a9 Version++ (0.5.0) 2019-08-30 15:07:04 +02:00
Benoit Marty fdc9e84dd5 Merge branch 'release/0.4.0' into develop 2019-08-30 15:04:43 +02:00
Benoit Marty 58f878fca9 Prepare version 0.4.0 2019-08-30 15:04:28 +02:00
Benoit Marty 88095e4bd9 Add entry in change file 2019-08-30 14:54:15 +02:00
Benoit Marty 47d22a3d5e Import translation from Riot and MatrixSDK 2019-08-30 11:21:43 +02:00
Valere 28e82cb8ea
Merge pull request #531 from vector-im/feature/fix_crash_530
Fix / EmojiCompat not initialized
2019-08-29 17:46:51 +02:00
Valere 35817245cb refactoring, code review 2019-08-29 17:27:49 +02:00
Valere 75266f42bb Fix / EmojiCompat not initialized 2019-08-29 16:49:22 +02:00
Benoit Marty 95c4c9ce56
Merge pull request #527 from vector-im/feature/privacy
Privacy: remove log of notifiable event (#519)
2019-08-29 12:16:34 +02:00
Benoit Marty ce5570105d Privacy: remove log of notifiable event (#519) 2019-08-29 10:36:45 +02:00
Benoit Marty 188a9aebfa
Merge pull request #525 from vector-im/feature/read_receipt_cleanup
Feature/read receipt cleanup
2019-08-29 10:19:06 +02:00
Benoit Marty c95223f5d2 Add long click support on unsupported event 2019-08-28 18:17:37 +02:00
Benoit Marty ef0362ba9c Display Read Receipt on unsupported events 2019-08-28 17:31:31 +02:00
Benoit Marty ea242f6737 Hide ReadReceipt View when it is not relevant 2019-08-28 17:17:37 +02:00
Valere cbc08d834b
Merge pull request #522 from vector-im/feature/fix_e2e_reply
Fix / regression on e2e reply and edit of reply
2019-08-28 10:38:22 +02:00
Valere 0ab6b33fb6
Merge branch 'develop' into feature/fix_e2e_reply 2019-08-28 10:38:12 +02:00
Valere 1b394527b6 cleaning + code review 2019-08-28 10:22:51 +02:00
Valere a8f1388721
Merge pull request #520 from vector-im/feature/read_receipts_511
Improve read receipt design
2019-08-28 10:17:56 +02:00
Valere 166be4e289 Improve read receipt design 2019-08-28 09:56:10 +02:00
Valere b49ccefe63
Merge pull request #521 from vector-im/feature/fix_dome_video_wont_play
Some video won't play
2019-08-28 03:43:35 -04:00
Valere 825760d17e Fix / regression on e2e reply and edit of reply 2019-08-27 17:05:04 +02:00
Valere b5af62c3ea Some video won't play
VideoView fails to play some remote uri video on some device. For now video is downloaded locally in internal cache then played. This offers basic support before full media preview implementation
2019-08-27 16:50:02 +02:00
Valere a51d96bf00
Merge pull request #325 from vector-im/feature/non_unicode_reaction
Accept non unicode reactions
2019-08-27 08:10:51 -04:00
Valere 7e142d201d Use EmojiCompat to build EmojiSpans from text 2019-08-27 11:06:52 +02:00
Valere 2be6058971 accept non unicode reactions 2019-08-27 10:58:21 +02:00
Valere 49d73f360e
Merge pull request #494 from vector-im/feature/fix_441
Fix text diff removed linebreak
2019-08-27 04:36:03 -04:00
Valere bd88d85a21
Merge branch 'develop' into feature/fix_441 2019-08-27 04:35:17 -04:00
Valere be4fc5cce6
Merge pull request #493 from vector-im/feature/fix_358
Date change message repeats for each redaction until a normal message
2019-08-27 04:34:35 -04:00
Valere 704da1be55
Merge branch 'develop' into feature/fix_358 2019-08-27 04:34:24 -04:00
Valere 5d002532d3
Merge pull request #495 from vector-im/feature/fix_423
Slide-in reply icon is distorted
2019-08-27 04:22:02 -04:00
Valere d4161e9a1a Fix text diff removed linebreak 2019-08-27 10:17:42 +02:00
Valere 7966ebef03 Date change message repeats for each redaction until a normal message 2019-08-27 10:16:11 +02:00
Valere ed5faca5d2 Slide-in reply icon is distorted 2019-08-27 10:06:20 +02:00
Benoit Marty 8ca829d538 An error was displayed by mistake 2019-08-19 17:22:04 +02:00
ganfra e7819ce678
Merge pull request #496 from vector-im/feature/di_clean
Dagger clean
2019-08-19 16:41:50 +02:00
ganfra 5402902bc2 Merge branch 'develop' into feature/di_clean 2019-08-19 15:04:26 +02:00
ganfra bc1350aaf5
Merge pull request #484 from vector-im/feature/timeline_read_receipts
Feature/timeline read receipts
2019-08-19 14:29:59 +02:00
ganfra fd74e3dfb1 Read receipts: clean code after review 2019-08-19 14:08:15 +02:00
ganfra e0628da1cb Dagger: use AssistedInjectModule for viewModel + use AssistedFactory for room dependencies 2019-08-14 19:09:56 +02:00
Benoit Marty aa4e74e986
Merge pull request #487 from vector-im/feature/fix_ui_issues
Feature/fix ui issues
2019-08-14 18:20:08 +02:00
Benoit Marty 6cc0c0672e
Merge pull request #474 from vector-im/feature/dev_suffix
Automatic "-dev" version suffix on non master branch
2019-08-14 18:15:44 +02:00
ganfra 501474b720 Fix code quality issues 2019-08-14 14:53:40 +02:00
ganfra e11c66035c Theme: the action menu text items should use colorAccent 2019-08-14 14:19:21 +02:00
ganfra 3d2d219d79 Room list: let the fab animation be quicker 2019-08-14 14:18:56 +02:00
ganfra 63af03bedd List: add overScroll 2019-08-14 14:18:42 +02:00
ganfra d3827b8673 Read receipts: branch settings to show/hide them 2019-08-14 10:51:09 +02:00
Benoit Marty 4ca2531e47 `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode
Other branches (master, features, etc.) will have version code based on application version.
2019-08-14 10:45:17 +02:00
ganfra 4e8dc72439 Update CHANGES 2019-08-13 15:17:04 +02:00
ganfra 25a4240a5a Merge branch 'develop' into feature/timeline_read_receipts 2019-08-13 15:16:10 +02:00
ganfra b9cfda23b6 Read receipts: just juste invisible on hidden avatars, to have a bigger touch zone 2019-08-13 15:06:00 +02:00
ganfra 06dcf75a32 Read receipts: fix not appearing RR 2019-08-13 12:06:49 +02:00
ganfra 21deb2551d Read receipts: handle read receipts set on filtered events + let BottomSheet takes a snapshot instead of being live. 2019-08-12 17:59:07 +02:00
ganfra 70639f180c Read receipts: add read receipts bottom sheet 2019-08-08 19:59:20 +02:00
ganfra 1dbb02a80d Read receipts: create custom view to use it wherever we want easily 2019-08-08 17:51:06 +02:00
ganfra 825463d9cd Change package for NotificationAreaView 2019-08-08 17:50:33 +02:00
ganfra c313ce78cb Read receipts: sort descending by timestamp 2019-08-08 17:49:50 +02:00
ganfra 39f58d048b Read receipts: fix dummy being overrided 2019-08-08 17:49:31 +02:00
Benoit Marty 3f792c7a84 Automatic "-dev" version suffix on non master branch 2019-08-08 16:57:03 +02:00
Benoit Marty 347dcb469a Version++ 2019-08-08 16:47:13 +02:00
Benoit Marty 79fb1985aa Merge branch 'release/0.3.0' into develop 2019-08-08 16:45:02 +02:00
Benoit Marty e216cd15a8 Prepare release 0.3.0 2019-08-08 16:44:53 +02:00
Benoit Marty 37fde374b3
Merge pull request #469 from vector-im/feature/versionCode_auto
Ensure versionCode is the wanted one for GPlay and F-Droid build
2019-08-08 16:32:10 +02:00
Benoit Marty f7b471f141 Stop using BuildConfig.VERSION_CODE, it is not the correct value 2019-08-08 16:31:45 +02:00
Benoit Marty 93fd56a7ca Ensure versionCode is the wanted one for GPlay and F-Droid build 2019-08-08 16:30:44 +02:00
Benoit Marty 5a9d88e791
Merge pull request #473 from vector-im/feature/sync_room
Feature/sync room
2019-08-08 16:15:26 +02:00
Benoit Marty eaf6a9923a Cancel sync request on pause and timeout to 0 after pause (#404) 2019-08-08 16:04:53 +02:00
ganfra d98567045c Read receipts: use a simpler strategy when it's initialSync 2019-08-08 15:03:36 +02:00
ganfra b4ce8748cb First step in handling read receipts 2019-08-08 14:32:11 +02:00
Benoit Marty 9d5433a857 Show sync progress also in room detail screen (#403) 2019-08-08 14:14:10 +02:00
Benoit Marty 6d4ee83e65
Merge pull request #472 from vector-im/feature/vectorPref
Dagger for VectorPreferences and /markdown command as a bonus
2019-08-08 12:43:22 +02:00
Benoit Marty 6e44cca17d Handle `/markdown` command 2019-08-08 12:09:05 +02:00
Benoit Marty 0a73887c70 Daggerization of VectorPreferences 2019-08-08 11:52:50 +02:00
Dominic Fischer 456908c851
Merge branch 'develop' into kt-remove_java_util 2019-08-06 18:27:39 +01:00
Dominic Fischer 215324a03e Some kotlinification
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-08-06 11:36:39 +01:00
Dominic Fischer 02e342849f Remove most usages of the java.util package
Signed-off-by: Dominic Fischer <dominicfischer7@gmail.com>
2019-07-21 23:23:56 +01:00
193 changed files with 5226 additions and 1528 deletions

View File

@ -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%



View File

@ -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 {

View File

@ -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,

View File

@ -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 {



View File

@ -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

View File

@ -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)
} }

View File

@ -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
) )
} }

View File

@ -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 {

View File

@ -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
) )

View File

@ -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)
} }

View File

@ -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>>
} }

View File

@ -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>()
@ -65,8 +67,8 @@ data class TimelineEvent(
"$name (${root.senderId})" "$name (${root.senderId})"
} }
} }
?: root.senderId ?: root.senderId
?: "" ?: ""
} }


/** /**
@ -94,7 +96,7 @@ fun TimelineEvent.hasBeenEdited() = annotations?.editSummary != null
* Get last MessageContent, after a possible edition * Get last MessageContent, after a possible edition
*/ */
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel() fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel() ?: root.getClearContent().toModel()




fun TimelineEvent.getTextEditableContent(): String? { fun TimelineEvent.getTextEditableContent(): String? {

View File

@ -25,12 +25,12 @@ interface TimelineService {


/** /**
* Instantiate a [Timeline] with an optional initial eventId, to be used with permalink. * Instantiate a [Timeline] with an optional initial eventId, to be used with permalink.
* You can 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?

View File

@ -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

)

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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 + ")"
} }
} }

View File

@ -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,

View File

@ -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

View File

@ -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? { private fun base64ToBase64Url(base64: String): String {
var result = base64 return base64.replace("\n".toRegex(), "")
if (null != result) { .replace("\\+".toRegex(), "-")
result = result.replace("\n".toRegex(), "") .replace('/', '_')
result = result.replace("\\+".toRegex(), "-") .replace("=", "")
result = result.replace("/".toRegex(), "_")
result = result.replace("=".toRegex(), "")
}
return result
} }


private fun base64ToUnpaddedBase64(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(), "")
}

return result
} }
} }

View File

@ -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

View File

@ -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

View File

@ -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) {



View File

@ -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(

View File

@ -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
} }


/** /**

View File

@ -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)
} }
} }

View File

@ -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
) )
} }

View File

@ -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()
} }





View File

@ -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)
@ -150,14 +176,14 @@ internal fun ChunkEntity.add(roomId: String,


internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> forwardsDisplayIndex PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue } ?: defaultValue
} }


internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int { internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) { return when (direction) {
PaginationDirection.FORWARDS -> forwardsStateIndex PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue } ?: defaultValue
} }

View File

@ -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())
}
}
}

}

View File

@ -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

View File

@ -17,29 +17,38 @@
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),
annotations = timelineEventEntity.annotations?.asDomain(), annotations = timelineEventEntity.annotations?.asDomain(),
localId = timelineEventEntity.localId, localId = timelineEventEntity.localId,
displayIndex = timelineEventEntity.root?.displayIndex ?: 0, displayIndex = timelineEventEntity.root?.displayIndex ?: 0,
senderName = timelineEventEntity.senderName, senderName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
senderAvatar = timelineEventEntity.senderAvatar senderAvatar = timelineEventEntity.senderAvatar,
readReceipts = readReceipts?.sortedByDescending {
it.originServerTs
} ?: emptyList()
) )
} }


} }


internal fun TimelineEventEntity.asDomain(): TimelineEvent {
return TimelineEventMapper.map(this)
}




View File

@ -17,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
} }

View File

@ -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

}

View File

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

View File

@ -30,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")

View File

@ -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"*}"""

}

View File

@ -26,4 +26,22 @@ internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, use
return realm.where<ReadReceiptEntity>() return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId) .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
.equalTo(ReadReceiptEntityFields.USER_ID, userId) .equalTo(ReadReceiptEntityFields.USER_ID, userId)
} }

internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
return ReadReceiptEntity().apply {
this.primaryKey = "${roomId}_$userId"
this.eventId = eventId
this.roomId = roomId
this.userId = userId
this.originServerTs = originServerTs
}
}

internal fun ReadReceiptEntity.Companion.getOrCreate(realm: Realm, roomId: String, userId: String): ReadReceiptEntity {
return ReadReceiptEntity.where(realm, roomId, userId).findFirst()
?: realm.createObject(ReadReceiptEntity::class.java, "${roomId}_$userId").apply {
this.roomId = roomId
this.userId = userId
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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



View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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

View File

@ -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



View File

@ -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) }
} }

View File

@ -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



View File

@ -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,
private val monarchy: Monarchy,
private val eventFactory: LocalEchoEventFactory,
private val roomSummaryMapper: RoomSummaryMapper,
private val taskExecutor: TaskExecutor,
private val loadRoomMembersTask: LoadRoomMembersTask,
private val inviteTask: InviteTask,
private val sendStateTask: SendStateTask,
private val paginationTask: PaginationTask,
private val contextOfEventTask: GetContextOfEventTask,
private val setReadMarkersTask: SetReadMarkersTask,
private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val fetchEditHistoryTask: FetchEditHistoryTask,
private val joinRoomTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask) {


fun create(roomId: String): Room { internal interface RoomFactory {
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, cryptoService, paginationTask) fun create(roomId: String): Room
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) }
val stateService = DefaultStateService(roomId, monarchy.realmConfiguration, taskExecutor, sendStateTask)
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials)
val relationService = DefaultRelationService(context,
credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, fetchEditHistoryTask, monarchy, taskExecutor)


internal class DefaultRoomFactory @Inject constructor(private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val cryptoService: CryptoService,
private val timelineServiceFactory: DefaultTimelineService.Factory,
private val sendServiceFactory: DefaultSendService.Factory,
private val stateServiceFactory: DefaultStateService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory) :
RoomFactory {

override fun create(roomId: String): Room {
return DefaultRoom( return DefaultRoom(
roomId, roomId,
monarchy, monarchy,
roomSummaryMapper, roomSummaryMapper,
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)
) )
} }



View File

@ -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



View File

@ -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,17 +33,21 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRo
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.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,
private val inviteTask: InviteTask, private val inviteTask: InviteTask,
private val joinTask: JoinRoomTask, private val joinTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask private val leaveRoomTask: LeaveRoomTask
) : MembershipService { ) : MembershipService {


@AssistedInject.Factory
interface Factory {
fun create(roomId: String): MembershipService
}

override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable { override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
return loadRoomMembersTask return loadRoomMembersTask

View File

@ -16,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)
@ -67,16 +81,28 @@ internal class DefaultReadService @Inject constructor(private val roomId: String
var isEventRead = false var isEventRead = false
monarchy.doWithRealm { monarchy.doWithRealm {
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst() val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
?: return@doWithRealm ?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
?: return@doWithRealm ?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
?: Int.MIN_VALUE ?: Int.MIN_VALUE
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
?: Int.MAX_VALUE ?: Int.MAX_VALUE
isEventRead = eventToCheckIndex <= readReceiptIndex isEventRead = eventToCheckIndex <= readReceiptIndex
} }
return isEventRead return isEventRead
} }


override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
val liveEntity = RealmLiveData(monarchy.realmConfiguration) { realm ->
ReadReceiptsSummaryEntity.where(realm, eventId)
}
return Transformations.map(liveEntity) { realmResults ->
realmResults.firstOrNull()?.let {
readReceiptsSummaryMapper.map(it)
}?.sortedByDescending {
it.originServerTs
} ?: emptyList()
}
}
} }

View File

@ -19,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,33 +47,33 @@ 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 credentials: Credentials, private val context: Context,
private val roomId: String, private val credentials: Credentials,
private val eventFactory: LocalEchoEventFactory, private val eventFactory: LocalEchoEventFactory,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val fetchEditHistoryTask: FetchEditHistoryTask, private val fetchEditHistoryTask: FetchEditHistoryTask,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor) private val taskExecutor: TaskExecutor)
: RelationService { : RelationService {


@AssistedInject.Factory
interface Factory {
fun create(roomId: String): RelationService
}

override fun sendReaction(reaction: String, targetEventId: String): Cancelable { override fun sendReaction(reaction: String, targetEventId: String): Cancelable {
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction) val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
.also { .also {
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> {

View File

@ -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 credentials: Credentials, private val context: Context,
private val roomId: String, private val credentials: Credentials,
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 {
@ -152,11 +166,11 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
override fun deleteFailedEcho(localEcho: TimelineEvent) { override fun deleteFailedEcho(localEcho: TimelineEvent) {
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
TimelineEventEntity.where(realm, eventId = localEcho.root.eventId TimelineEventEntity.where(realm, eventId = localEcho.root.eventId
?: "").findFirst()?.let { ?: "").findFirst()?.let {
it.deleteFromRealm() it.deleteFromRealm()
} }
EventEntity.where(realm, eventId = localEcho.root.eventId EventEntity.where(realm, eventId = localEcho.root.eventId
?: "").findFirst()?.let { ?: "").findFirst()?.let {
it.deleteFromRealm() it.deleteFromRealm()
} }
} }

View File

@ -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 {
val lines = body.text.split("\n") return buildString {
val replyFallback = StringBuffer("> <$originalSenderId>") append("> <")
lines.forEachIndexed { index, s -> append(originalSenderId)
if (index == 0) { append(">")
replyFallback.append(" $s")
} else { val lines = body.text.split("\n")
replyFallback.append("\n> $s") lines.forEachIndexed { index, s ->
if (index == 0) {
append(" $s")
} else {
append("\n> $s")
}
} }
append("\n\n")
append(newBodyText)
} }
replyFallback.append("\n\n").append(newBodyText)
return replyFallback.toString()
} }


/** /**

View File

@ -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 ->
@ -45,10 +52,10 @@ internal class DefaultStateService @Inject constructor(private val roomId: Strin


override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) { override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
val params = SendStateTask.Params(roomId, val params = SendStateTask.Params(roomId,
EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_TOPIC,
mapOf( mapOf(
"topic" to topic "topic" to topic
)) ))




sendStateTask sendStateTask

View File

@ -19,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,12 +269,28 @@ internal class DefaultTimeline(
return hasMoreInCache(direction) || !hasReachedEnd(direction) return hasMoreInCache(direction) || !hasReachedEnd(direction)
} }


// TimelineHiddenReadReceipts.Delegate

override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean {
return builtEventsIdMap[eventId]?.let { builtIndex ->
//Update the relation of existing event
builtEvents[builtIndex]?.let { te ->
builtEvents[builtIndex] = te.copy(readReceipts = readReceipts)
true
}
} ?: false
}

override fun onReadReceiptsUpdated() {
postSnapshot()
}

// Private methods ***************************************************************************** // Private methods *****************************************************************************


private fun hasMoreInCache(direction: Timeline.Direction): Boolean { private fun hasMoreInCache(direction: Timeline.Direction): Boolean {
return Realm.getInstance(realmConfiguration).use { localRealm -> return Realm.getInstance(realmConfiguration).use { localRealm ->
val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction) val timelineEventEntity = buildEventQuery(localRealm).findFirst(direction)
?: return false ?: return false
if (direction == Timeline.Direction.FORWARDS) { if (direction == Timeline.Direction.FORWARDS) {
if (findCurrentChunk(localRealm)?.isLastForward == true) { if (findCurrentChunk(localRealm)?.isLastForward == true) {
return false return false
@ -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 {
@ -395,9 +415,9 @@ internal class DefaultTimeline(
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
val token = getTokenLive(direction) ?: return val token = getTokenLive(direction) ?: return
val params = PaginationTask.Params(roomId = roomId, val params = PaginationTask.Params(roomId = roomId,
from = token, from = token,
direction = direction.toPaginationDirection(), direction = direction.toPaginationDirection(),
limit = limit) limit = limit)


Timber.v("Should fetch $limit items $direction") Timber.v("Should fetch $limit items $direction")
cancelableBag += paginationTask cancelableBag += paginationTask
@ -463,10 +483,11 @@ internal class DefaultTimeline(
nextDisplayIndex = offsetIndex + 1 nextDisplayIndex = offsetIndex + 1
} }
offsetResults.forEach { eventEntity -> offsetResults.forEach { eventEntity ->
val timelineEvent = eventEntity.asDomain()
val timelineEvent = buildTimelineEvent(eventEntity)


if (timelineEvent.isEncrypted() if (timelineEvent.isEncrypted()
&& timelineEvent.root.mxDecryptionResult == null) { && timelineEvent.root.mxDecryptionResult == null) {
timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) } timelineEvent.root.eventId?.let { eventDecryptor.requestDecryption(it) }
} }


@ -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(

View File

@ -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) }
} }
} }



View File

@ -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
}


}

View File

@ -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)
} }

View File

@ -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
val primaryKey = roomId + userId
ReadReceiptEntity(primaryKey, userId, eventId, roomId, ts)
}
}
}
} }


private fun initialSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
val readReceiptSummaries = ArrayList<ReadReceiptsSummaryEntity>()
for ((eventId, receiptDict) in content) {
val userIdsDict = receiptDict[READ_KEY] ?: continue
val readReceiptsSummary = ReadReceiptsSummaryEntity(eventId = eventId, roomId = roomId)

for ((userId, paramsDict) in userIdsDict) {
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
val receiptEntity = ReadReceiptEntity.createUnmanaged(roomId, eventId, userId, ts)
readReceiptsSummary.readReceipts.add(receiptEntity)
}
readReceiptSummaries.add(readReceiptsSummary)
}
realm.insertOrUpdate(readReceiptSummaries)
}

private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) {
for ((eventId, receiptDict) in content) {
val userIdsDict = receiptDict[READ_KEY] ?: continue
val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst()
?: realm.createObject(ReadReceiptsSummaryEntity::class.java, eventId).apply {
this.roomId = roomId
}

for ((userId, paramsDict) in userIdsDict) {
val ts = paramsDict[TIMESTAMP_KEY] ?: 0.0
val receiptEntity = ReadReceiptEntity.getOrCreate(realm, roomId, userId)
// ensure new ts is superior to the previous one
if (ts > receiptEntity.originServerTs) {
ReadReceiptsSummaryEntity.where(realm, receiptEntity.eventId).findFirst()?.also {
it.readReceipts.remove(receiptEntity)
}
receiptEntity.eventId = eventId
receiptEntity.originServerTs = ts
readReceiptsSummary.readReceipts.add(receiptEntity)
}
}
}
}


} }

View File

@ -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) }

View File

@ -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)

View File

@ -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 {

View File

@ -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,8 +151,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
.executeBy(taskExecutor) .executeBy(taskExecutor)


latch.await() latch.await()
if (state is SyncState.RUNNING) { state.let {
updateStateTo(SyncState.RUNNING(afterPause = false)) if (it is SyncState.RUNNING && it.afterPause) {
updateStateTo(SyncState.RUNNING(afterPause = false))
}
} }


Timber.v("...Continue") Timber.v("...Continue")

View File

@ -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 {

View File

@ -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

View File

@ -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()

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 denvoi</string>

</resources> </resources>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -48,7 +48,7 @@
<string name="notice_room_third_party_registered_invite">%1$s èt duutnodigienge vo %2$s anveird</string> <string name="notice_room_third_party_registered_invite">%1$s èt duutnodigienge 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>

View File

@ -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>

View File

@ -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>

View File

@ -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'

View File

@ -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()
} }
} }

View File

@ -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")

View File

@ -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)
} }
} }

View File

@ -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
} }
} }

View 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
}
}
}

View File

@ -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())



View File

@ -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()
}

} }

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

} }

View File

@ -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



View File

@ -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()
}

} }

View File

@ -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