Merge branch 'release/0.8.0'

This commit is contained in:
Benoit Marty 2019-11-19 09:47:57 +01:00
commit eb32c5455f
373 changed files with 7180 additions and 3234 deletions

View File

@ -3,14 +3,36 @@
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
# We propagate the environment to the container (sse https://github.com/buildkite-plugins/docker-buildkite-plugin#propagate-environment-optional-boolean)
# Build debug version of the RiotX application, from the develop branch and the features branches
steps:
- label: "Assemble GPlay Debug version"
- label: "Compile and run Unit tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build is long
# gradle build can be memory hungry
queue: "medium"
commands:
- "./gradlew clean test --stacktrace"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Compile Android tests"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "medium"
commands:
- "./gradlew clean assembleAndroidTest --stacktrace"
plugins:
- docker#v3.1.0:
image: "runmymind/docker-android-sdk"
propagate-environment: true
- label: "Assemble GPlay Debug version"
agents:
# We use a xlarge sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "xlarge"
commands:
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
artifact_paths:
@ -23,9 +45,9 @@ steps:
- label: "Assemble FDroid Debug version"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build is long
queue: "medium"
# We use a xlarge sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "xlarge"
commands:
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
artifact_paths:
@ -38,9 +60,9 @@ steps:
- label: "Build Google Play unsigned APK"
agents:
# We use a medium sized instance instead of the normal small ones because
# gradle build is long
queue: "medium"
# We use a xlarge sized instance instead of the normal small ones because
# gradle build can be memory hungry
queue: "xlarge"
commands:
- "./gradlew clean assembleGplayRelease --stacktrace"
artifact_paths:

View File

@ -1,3 +1,26 @@
Changes in RiotX 0.8.0 (2019-11-19)
===================================================
Features ✨:
- Handle long click on room in the room list (#395)
- Ignore/UnIgnore users, and display list of ignored users (#542, #617)
Improvements 🙌:
- Search reaction by name or keyword in emoji picker
- Handle code tags (#567)
- Support spoiler messages
- Support m.sticker and m.room.join_rules events in timeline
Other changes:
- Markdown set to off by default (#412)
- Accessibility improvements to the attachment file type chooser
Bugfix 🐛:
- Fix issues with some member events rendering (#498)
- Passphrase does not match (Export room keys) (#644)
- Ask for permission to write external storage when uri comes from the keyboard (#658)
- Fix issue with english US/GB translation (#671)
Changes in RiotX 0.7.0 (2019-10-24)
===================================================
@ -12,6 +35,7 @@ Improvements:
- Attachments: start using system pickers (#52)
- Mark all messages as read (#396)
Other changes:
- Accessibility improvements to read receipts in the room timeline and reactions emoji chooser

View File

@ -86,6 +86,10 @@ Also, if possible, please test your change on a real device. Testing on Android
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
Do not hesitate to use plurals when appropriate.
### Accessibility
Please consider accessibility as an important point. As a minimum requirement, in layout XML files please use attributes such as `android:contentDescription` and `android:importantForAccessibility`, and test with a screen reader if it's working well. You can add new string resources, dedicated to accessibility, in this case, please prefix theirs id with `a11y_`.
### Layout
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.

View File

@ -11,6 +11,8 @@ android {
versionCode 1
versionName "1.0"
// Multidex is useful for tests
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.rx;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
}
}

View File

@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional
@ -67,6 +68,10 @@ class RxRoom(private val room: Room) {
fun liveDrafts(): Observable<List<UserDraft>> {
return room.getDraftsLive().asObservable()
}
fun liveNotificationState(): Observable<RoomNotificationState> {
return room.getLiveRoomNotificationState().asObservable()
}
}
fun Room.rx(): RxRoom {

View File

@ -54,6 +54,10 @@ class RxSession(private val session: Session) {
return session.liveUsers().asObservable()
}
fun liveIgnoredUsers(): Observable<List<User>> {
return session.liveIgnoredUsers().asObservable()
}
fun livePagedUsers(filter: String? = null): Observable<PagedList<User>> {
return session.livePagedUsers(filter).asObservable()
}

View File

@ -155,7 +155,8 @@ dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.3'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
testImplementation 'io.mockk:mockk:1.9.3.kotlin12'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.44'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
@ -165,7 +166,8 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
androidTestImplementation 'io.mockk:mockk-android:1.9.3.kotlin12'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

View File

@ -17,12 +17,12 @@
package im.vector.matrix.android
import android.content.Context
import androidx.test.InstrumentationRegistry
import androidx.test.core.app.ApplicationProvider
import java.io.File
interface InstrumentedTest {
fun context(): Context {
return InstrumentationRegistry.getTargetContext()
return ApplicationProvider.getApplicationContext()
}
fun cacheDir(): File {

View File

@ -17,8 +17,8 @@
package im.vector.matrix.android.auth
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.rule.GrantPermissionRule
import androidx.test.runner.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.OkReplayRuleChainNoActivity
import im.vector.matrix.android.api.auth.Authenticator

View File

@ -67,5 +67,5 @@ interface Authenticator {
/**
* Create a session after a SSO successful login
*/
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session
fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<Session>): Cancelable
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.crypto
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
import im.vector.matrix.android.internal.crypto.verification.getEmojiForCode
/**
* Provide all the emojis used for SAS verification (for debug purpose)
*/
fun getAllVerificationEmojis(): List<EmojiRepresentation> {
return (0..63).map { getEmojiForCode(it) }
}

View File

@ -21,7 +21,7 @@ import timber.log.Timber
sealed class Action {
object Notify : Action()
object DoNotNotify : Action()
data class Sound(val sound: String) : Action()
data class Sound(val sound: String = ACTION_OBJECT_VALUE_VALUE_DEFAULT) : Action()
data class Highlight(val highlight: Boolean) : Action()
}
@ -63,6 +63,29 @@ private const val ACTION_OBJECT_VALUE_VALUE_DEFAULT = "default"
*
* </pre>
*/
@Suppress("IMPLICIT_CAST_TO_ANY")
fun List<Action>.toJson(): List<Any> {
return map { action ->
when (action) {
is Action.Notify -> ACTION_NOTIFY
is Action.DoNotNotify -> ACTION_DONT_NOTIFY
is Action.Sound -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_SOUND,
ACTION_OBJECT_VALUE_KEY to action.sound
)
}
is Action.Highlight -> {
mapOf(
ACTION_OBJECT_SET_TWEAK_KEY to ACTION_OBJECT_SET_TWEAK_VALUE_HIGHLIGHT,
ACTION_OBJECT_VALUE_KEY to action.highlight
)
}
}
}
}
fun PushRule.getActions(): List<Action> {
val result = ArrayList<Action>()

View File

@ -34,6 +34,10 @@ interface PushRuleService {
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
fun addPushRuleListener(listener: PushRuleListener)
fun removePushRuleListener(listener: PushRuleListener)

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session.cache
import im.vector.matrix.android.api.MatrixCallback
/**
* This interface defines a method to sign out. It's implemented at the session level.
* This interface defines a method to clear the cache. It's implemented at the session level.
*/
interface CacheService {

View File

@ -16,7 +16,7 @@
package im.vector.matrix.android.api.session.events.model
import java.util.*
import java.util.UUID
object LocalEcho {

View File

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.DraftService
@ -41,7 +42,8 @@ interface Room :
StateService,
ReportingService,
RelationService,
RoomCryptoService {
RoomCryptoService,
RoomPushRuleService {
/**
* The roomId of this room

View File

@ -0,0 +1,38 @@
/*
* 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.model
import com.squareup.moshi.Json
/**
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
*/
enum class RoomJoinRules(val value: String) {
@Json(name = "public")
PUBLIC("public"),
@Json(name = "invite")
INVITE("invite"),
@Json(name = "knock")
KNOCK("knock"),
@Json(name = "private")
PRIVATE("private")
}

View File

@ -0,0 +1,29 @@
/*
* 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.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Class representing the EventType.STATE_ROOM_JOIN_RULES state event content
*/
@JsonClass(generateAdapter = true)
data class RoomJoinRulesContent(
@Json(name = "join_rule") val joinRules: RoomJoinRules? = null
)

View File

@ -38,7 +38,7 @@ data class MessageImageContent(
/**
* Metadata about the image referred to in url.
*/
@Json(name = "info") val info: ImageInfo? = null,
@Json(name = "info") override val info: ImageInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
@ -52,4 +52,4 @@ data class MessageImageContent(
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageEncryptedContent
) : MessageImageInfoContent

View File

@ -0,0 +1,25 @@
/*
* 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.model.message
/**
* A content with image information
*/
interface MessageImageInfoContent : MessageEncryptedContent {
val info: ImageInfo?
}

View File

@ -0,0 +1,56 @@
/*
* 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.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedFileInfo
@JsonClass(generateAdapter = true)
data class MessageStickerContent(
/**
* Set in local, not from server
*/
override val type: String = MessageType.MSGTYPE_STICKER_LOCAL,
/**
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
* or some kind of content description for accessibility e.g. 'image attachment'.
*/
@Json(name = "body") override val body: String,
/**
* Metadata about the image referred to in url.
*/
@Json(name = "info") override val info: ImageInfo? = null,
/**
* Required. Required if the file is unencrypted. The URL (typically MXC URI) to the image.
*/
@Json(name = "url") override val url: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null,
/**
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
*/
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent

View File

@ -0,0 +1,42 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.notification
/**
* Defines the room notification state
*/
enum class RoomNotificationState {
/**
* All the messages will trigger a noisy notification
*/
ALL_MESSAGES_NOISY,
/**
* All the messages will trigger a notification
*/
ALL_MESSAGES,
/**
* Only the messages with user display name / user name will trigger notifications
*/
MENTIONS_ONLY,
/**
* No notifications
*/
MUTE
}

View File

@ -0,0 +1,28 @@
/*
* 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.notification
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
interface RoomPushRuleService {
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
}

View File

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
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.internal.crypto.model.event.EncryptedEventContent
@ -62,15 +63,11 @@ data class TimelineEvent(
}
fun getDisambiguatedDisplayName(): String {
return if (isUniqueDisplayName) {
senderName
} else {
senderName?.let { name ->
"$name (${root.senderId})"
}
return when {
senderName.isNullOrBlank() -> root.senderId ?: ""
isUniqueDisplayName -> senderName
else -> "$senderName (${root.senderId})"
}
?: root.senderId
?: ""
}
/**
@ -103,8 +100,14 @@ fun TimelineEvent.getEditedEventId(): String? {
/**
* Get last MessageContent, after a possible edition
*/
fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel()
fun TimelineEvent.getLastMessageContent(): MessageContent? {
return if (root.getClearType() == EventType.STICKER) {
root.getClearContent().toModel<MessageStickerContent>()
} else {
annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel()
}
}
/**
* Get last Message body, after a possible edition
@ -113,7 +116,8 @@ fun TimelineEvent.getLastMessageBody(): String? {
val lastMessageContent = getLastMessageContent()
if (lastMessageContent != null) {
return lastMessageContent.newContent?.toModel<MessageContent>()?.body ?: lastMessageContent.body
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
?: lastMessageContent.body
}
return null

View File

@ -64,4 +64,19 @@ interface UserService {
* @return a Livedata of users
*/
fun livePagedUsers(filter: String? = null): LiveData<PagedList<User>>
/**
* Get list of ignored users
*/
fun liveIgnoredUsers(): LiveData<List<User>>
/**
* Ignore users
*/
fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
/**
* Un-ignore some users
*/
fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable
}

View File

@ -21,4 +21,6 @@ import java.lang.reflect.ParameterizedType
typealias JsonDict = Map<String, @JvmSuppressWildcards Any>
val emptyJsonDict = emptyMap<String, Any>()
internal val JSON_DICT_PARAMETERIZED_TYPE: ParameterizedType = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.auth
import android.util.Patterns
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.Credentials
@ -39,10 +40,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import javax.inject.Inject
import javax.inject.Provider
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val okHttpClient: Provider<OkHttpClient>,
private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
@ -112,14 +112,27 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
sessionManager.getOrCreateSession(sessionParams)
}
override fun createSessionFromSso(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
override fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig,
callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = runCatching {
createSessionFromSso(credentials, homeServerConnectionConfig)
}
sessionOrFailure.foldToCallback(callback)
}
return CancelableCoroutine(job)
}
private suspend fun createSessionFromSso(credentials: Credentials,
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams)
sessionManager.getOrCreateSession(sessionParams)
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(okHttpClient.get(), homeServerConnectionConfig.homeServerUri.toString())
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}
}

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.auth
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
internal interface SessionParamsStore {
@ -27,9 +26,9 @@ internal interface SessionParamsStore {
fun getAll(): List<SessionParams>
fun save(sessionParams: SessionParams): Try<Unit>
suspend fun save(sessionParams: SessionParams)
fun delete(userId: String): Try<Unit>
suspend fun delete(userId: String)
fun deleteAll(): Try<Unit>
suspend fun deleteAll()
}

View File

@ -16,9 +16,9 @@
package im.vector.matrix.android.internal.auth.db
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.di.AuthDatabase
import io.realm.Realm
import io.realm.RealmConfiguration
@ -62,41 +62,29 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
return sessionParams
}
override fun save(sessionParams: SessionParams): Try<Unit> {
return Try {
override suspend fun save(sessionParams: SessionParams) {
awaitTransaction(realmConfiguration) {
val entity = mapper.map(sessionParams)
if (entity != null) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.insert(entity)
}
realm.close()
it.insert(entity)
}
}
}
override fun delete(userId: String): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
realm.close()
override suspend fun delete(userId: String) {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
}
override fun deleteAll(): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
realm.close()
override suspend fun deleteAll() {
awaitTransaction(realmConfiguration) {
it.where(SessionParamsEntity::class.java)
.findAll()
.deleteAllFromRealm()
}
}
}

View File

@ -66,7 +66,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
if (':' in userId) {
try {
synchronized(notReadyToRetryHS) {
res = !notReadyToRetryHS.contains(userId.substring(userId.lastIndexOf(":") + 1))
res = !notReadyToRetryHS.contains(userId.substringAfterLast(':'))
}
} catch (e: Exception) {
Timber.e(e, "## canRetryKeysDownload() failed")

View File

@ -216,7 +216,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor(
sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback<Unit> {
private fun onDone(state: OutgoingRoomKeyRequest.RequestState) {
if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) {
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to " + request.state)
Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to ${request.state}")
} else {
request.state = state
cryptoStore.updateOutgoingRoomKeyRequest(request)

View File

@ -41,8 +41,7 @@ fun <T> doWithRealm(realmConfiguration: RealmConfiguration, action: (Realm) -> T
*/
fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm)
result?.let { realm.copyFromRealm(it) }
action.invoke(realm)?.let { realm.copyFromRealm(it) }
}
}
@ -51,8 +50,7 @@ fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration
*/
fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable<T>): Iterable<T> {
return Realm.getInstance(realmConfiguration).use { realm ->
val result = action.invoke(realm)
realm.copyFromRealm(result)
action.invoke(realm).let { realm.copyFromRealm(it) }
}
}

View File

@ -91,7 +91,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
realmLocker = Realm.getInstance(realmConfiguration)
// Ensure CryptoMetadataEntity is inserted in DB
doWithRealm(realmConfiguration) { realm ->
doRealmTransaction(realmConfiguration) { realm ->
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
var deleteAll = false
@ -109,15 +109,13 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
if (currentMetadata == null) {
realm.executeTransaction {
if (deleteAll) {
it.deleteAll()
}
if (deleteAll) {
realm.deleteAll()
}
// Metadata not found, or database cleaned, create it
it.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply {
deviceId = credentials.deviceId
}
// Metadata not found, or database cleaned, create it
realm.createObject(CryptoMetadataEntity::class.java, credentials.userId).apply {
deviceId = credentials.deviceId
}
}
}

View File

@ -43,6 +43,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.lang.Exception
import java.util.UUID
import javax.inject.Inject
import kotlin.collections.HashMap
@ -166,72 +167,59 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
return
}
// Download device keys prior to everything
checkKeysAreDownloaded(
otherUserId!!,
startReq,
success = {
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
val tid = startReq.transactionID!!
val existing = getExistingTransaction(otherUserId, tid)
val existingTxs = getExistingTransactionsForUser(otherUserId)
if (existing != null) {
// should cancel both!
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
existing.cancel(CancelCode.UnexpectedMessage)
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else if (existingTxs?.isEmpty() == false) {
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
existingTxs.forEach {
it.cancel(CancelCode.UnexpectedMessage)
}
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else {
// Ok we can create
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
val tx = IncomingSASVerificationTransaction(
this,
setDeviceVerificationAction,
credentials,
cryptoStore,
sendToDeviceTask,
taskExecutor,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionID!!,
otherUserId)
addTransaction(tx)
tx.acceptToDeviceEvent(otherUserId, startReq)
} else {
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
cancelTransaction(tid, otherUserId, startReq.fromDevice
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
}
}
},
error = {
cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
})
if (checkKeysAreDownloaded(otherUserId!!, startReq) != null) {
Timber.v("## SAS onStartRequestReceived ${startReq.transactionID!!}")
val tid = startReq.transactionID!!
val existing = getExistingTransaction(otherUserId, tid)
val existingTxs = getExistingTransactionsForUser(otherUserId)
if (existing != null) {
// should cancel both!
Timber.v("## SAS onStartRequestReceived - Request exist with same if ${startReq.transactionID!!}")
existing.cancel(CancelCode.UnexpectedMessage)
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else if (existingTxs?.isEmpty() == false) {
Timber.v("## SAS onStartRequestReceived - There is already a transaction with this user ${startReq.transactionID!!}")
// Multiple keyshares between two devices: any two devices may only have at most one key verification in flight at a time.
existingTxs.forEach {
it.cancel(CancelCode.UnexpectedMessage)
}
cancelTransaction(tid, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
} else {
// Ok we can create
if (KeyVerificationStart.VERIF_METHOD_SAS == startReq.method) {
Timber.v("## SAS onStartRequestReceived - request accepted ${startReq.transactionID!!}")
val tx = IncomingSASVerificationTransaction(
this,
setDeviceVerificationAction,
credentials,
cryptoStore,
sendToDeviceTask,
taskExecutor,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionID!!,
otherUserId)
addTransaction(tx)
tx.acceptToDeviceEvent(otherUserId, startReq)
} else {
Timber.e("## SAS onStartRequestReceived - unknown method ${startReq.method}")
cancelTransaction(tid, otherUserId, startReq.fromDevice
?: event.getSenderKey()!!, CancelCode.UnknownMethod)
}
}
} else {
cancelTransaction(startReq.transactionID!!, otherUserId, startReq.fromDevice!!, CancelCode.UnexpectedMessage)
}
}
private suspend fun checkKeysAreDownloaded(otherUserId: String,
startReq: KeyVerificationStart,
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
error: () -> Unit) {
runCatching {
deviceListManager.downloadKeys(listOf(otherUserId), true)
}.fold(
{
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
success(it)
} else {
error()
}
},
{
error()
}
)
startReq: KeyVerificationStart): MXUsersDevicesMap<MXDeviceInfo>? {
return try {
val keys = deviceListManager.downloadKeys(listOf(otherUserId), true)
val deviceIds = keys.getUserDeviceIds(otherUserId) ?: return null
keys.takeIf { deviceIds.contains(startReq.fromDevice) }
} catch (e: Exception) {
null
}
}
private suspend fun onCancelReceived(event: Event) {
@ -342,10 +330,8 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
private fun addTransaction(tx: VerificationTransaction) {
tx.otherUserId.let { otherUserId ->
synchronized(txMap) {
if (txMap[otherUserId] == null) {
txMap[otherUserId] = HashMap()
}
txMap[otherUserId]?.set(tx.transactionId, tx)
val txInnerMap = txMap.getOrPut(otherUserId) { HashMap() }
txInnerMap[tx.transactionId] = tx
dispatchTxAdded(tx)
tx.addListener(this)
}

View File

@ -20,14 +20,19 @@ import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import timber.log.Timber
suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> Unit) = withContext(Dispatchers.IO) {
suspend fun awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> Unit) = withContext(Dispatchers.Default) {
Realm.getInstance(config).use { bgRealm ->
bgRealm.beginTransaction()
try {
val start = System.currentTimeMillis()
transaction(bgRealm)
if (isActive) {
bgRealm.commitTransaction()
val end = System.currentTimeMillis()
val time = end - start
Timber.v("Execute transaction in $time millis")
}
} finally {
if (bgRealm.isInTransaction) {

View File

@ -39,14 +39,17 @@ internal fun TimelineEventEntity.updateSenderData() {
val isUnlinked = chunkEntity.isUnlinked()
var senderMembershipEvent: EventEntity?
var senderRoomMemberContent: String?
var senderRoomMemberPrevContent: String?
when {
stateIndex <= 0 -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).next(from = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.prevContent
senderRoomMemberPrevContent = senderMembershipEvent?.content
}
else -> {
senderMembershipEvent = chunkEntity.timelineEvents.buildQuery(senderId, isUnlinked).prev(since = stateIndex)?.root
senderRoomMemberContent = senderMembershipEvent?.content
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
}
@ -58,11 +61,27 @@ internal fun TimelineEventEntity.updateSenderData() {
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.prev(since = stateIndex)
senderRoomMemberContent = senderMembershipEvent?.content
senderRoomMemberPrevContent = senderMembershipEvent?.prevContent
}
ContentMapper.map(senderRoomMemberContent).toModel<RoomMember>()?.also {
this.senderAvatar = it.avatarUrl
this.senderName = it.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
}
// We try to fallback on prev content if we got a room member state events with null fields
if (root?.type == EventType.STATE_ROOM_MEMBER) {
ContentMapper.map(senderRoomMemberPrevContent).toModel<RoomMember>()?.also {
if (this.senderAvatar == null && it.avatarUrl != null) {
this.senderAvatar = it.avatarUrl
}
if (this.senderName == null && it.displayName != null) {
this.senderName = it.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(it.displayName)
}
}
}
val senderRoomMember: RoomMember? = ContentMapper.map(senderRoomMemberContent).toModel()
this.senderAvatar = senderRoomMember?.avatarUrl
this.senderName = senderRoomMember?.displayName
this.isUniqueDisplayName = RoomMembers(realm, roomId).isUniqueDisplayName(senderRoomMember?.displayName)
this.senderMembershipEvent = senderMembershipEvent
}

View File

@ -22,7 +22,7 @@ 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.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import java.util.*
import java.util.UUID
import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor(
@ -36,7 +36,7 @@ internal class RoomSummaryMapper @Inject constructor(
}
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it)
timelineEventMapper.map(it, buildReadReceipts = false)
}
if (latestEvent?.root?.isEncrypted() == true && latestEvent.root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?

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.database.model
import io.realm.RealmObject
internal open class IgnoredUserEntity(var userId: String = "") : RealmObject() {
companion object
}

View File

@ -17,6 +17,8 @@ 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
internal open class PushRuleEntity(
// Required. The actions to perform when this rule is matched.
@ -33,5 +35,8 @@ internal open class PushRuleEntity(
var pattern: String? = null
) : RealmObject() {
@LinkingObjects("pushRules")
val parent: RealmResults<PushRulesEntity>? = null
companion object
}

View File

@ -35,6 +35,7 @@ import io.realm.annotations.RealmModule
RoomTagEntity::class,
SyncEntity::class,
UserEntity::class,
IgnoredUserEntity::class,
EventAnnotationsSummaryEntity::class,
ReactionAggregatedSummaryEntity::class,
EditAggregatedSummaryEntity::class,

View File

@ -16,27 +16,26 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.database.model.FilterEntity
import im.vector.matrix.android.internal.session.filter.FilterFactory
import io.realm.Realm
import io.realm.kotlin.createObject
import io.realm.kotlin.where
/**
* Get the current filter, create one if it does not exist
*/
internal fun FilterEntity.Companion.getFilter(realm: Realm): FilterEntity {
internal suspend fun FilterEntity.Companion.getFilter(realm: Realm): FilterEntity {
var filter = realm.where<FilterEntity>().findFirst()
if (filter == null) {
realm.executeTransaction {
realm.createObject<FilterEntity>().apply {
filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString()
roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString()
filterId = ""
}
filter = FilterEntity().apply {
filterBodyJson = FilterFactory.createDefaultFilterBody().toJSONString()
roomEventFilterJson = FilterFactory.createDefaultRoomFilter().toJSONString()
filterId = ""
}
awaitTransaction(realm.configuration) {
it.insert(filter)
}
filter = realm.where<FilterEntity>().findFirst()!!
}
return filter
}

View File

@ -16,10 +16,10 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PushRulesEntityFields
import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
@ -41,3 +41,11 @@ internal fun PushRulesEntity.Companion.where(realm: Realm,
.equalTo(PushRulesEntityFields.SCOPE, scope)
.equalTo(PushRulesEntityFields.KIND_STR, kind.name)
}
internal fun PushRuleEntity.Companion.where(realm: Realm,
scope: String,
ruleId: String): RealmQuery<PushRuleEntity> {
return realm.where<PushRuleEntity>()
.equalTo("${PushRuleEntityFields.PARENT}.${PushRulesEntityFields.SCOPE}", scope)
.equalTo(PushRuleEntityFields.RULE_ID, ruleId)
}

View File

@ -24,8 +24,7 @@ import io.realm.kotlin.where
internal fun ReadReceiptEntity.Companion.where(realm: Realm, roomId: String, userId: String): RealmQuery<ReadReceiptEntity> {
return realm.where<ReadReceiptEntity>()
.equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
.equalTo(ReadReceiptEntityFields.USER_ID, userId)
.equalTo(ReadReceiptEntityFields.PRIMARY_KEY, buildPrimaryKey(roomId, userId))
}
internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: String): RealmQuery<ReadReceiptEntity> {
@ -45,8 +44,10 @@ internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId
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 {
?: realm.createObject(ReadReceiptEntity::class.java, buildPrimaryKey(roomId, userId)).apply {
this.roomId = roomId
this.userId = userId
}
}
private fun buildPrimaryKey(roomId: String, userId: String) = "${roomId}_$userId"

View File

@ -111,7 +111,7 @@ internal fun RealmQuery<TimelineEventEntity>.prev(since: Int? = null, strict: Bo
internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
return this.where()
.equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, eventId)
.equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
.findFirst()
}

View File

@ -36,7 +36,7 @@ internal object MatrixModule {
@MatrixScope
fun providesMatrixCoroutineDispatchers(): MatrixCoroutineDispatchers {
return MatrixCoroutineDispatchers(io = Dispatchers.IO,
computation = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main,
crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(),
sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher()

View File

@ -20,10 +20,11 @@ import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import im.vector.matrix.android.internal.session.sync.model.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules
object MoshiProvider {
@ -31,6 +32,7 @@ object MoshiProvider {
.add(UriMoshiAdapter())
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
.registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
.registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
)
.add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java)

View File

@ -22,7 +22,7 @@ import com.novoda.merlin.MerlinsBeard
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import timber.log.Timber
import java.util.*
import java.util.Collections
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

View File

@ -17,17 +17,24 @@
package im.vector.matrix.android.internal.network
import com.squareup.moshi.Moshi
import dagger.Lazy
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Request
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Inject
class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.callFactory(object : Call.Factory {
override fun newCall(request: Request): Call {
return okHttpClient.get().newCall(request)
}
})
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session
import android.content.Context
import com.zhuinden.monarchy.Monarchy
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
@ -132,7 +133,7 @@ internal abstract class SessionModule {
@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(@Authenticated okHttpClient: OkHttpClient,
fun providesRetrofit(@Authenticated okHttpClient: Lazy<OkHttpClient>,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory

View File

@ -16,54 +16,44 @@
package im.vector.matrix.android.internal.session.filter
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.FilterEntity
import im.vector.matrix.android.internal.database.model.FilterEntityFields
import im.vector.matrix.android.internal.database.query.getFilter
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.where
import javax.inject.Inject
internal class DefaultFilterRepository @Inject constructor(
@SessionDatabase private val realmConfiguration: RealmConfiguration
) : FilterRepository {
internal class DefaultFilterRepository @Inject constructor(private val monarchy: Monarchy) : FilterRepository {
override fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean {
val result: Boolean
override suspend fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
val filter = FilterEntity.getFilter(realm)
val result = if (filter.filterBodyJson != filterBody.toJSONString()) {
// Filter has changed, store it and reset the filter Id
monarchy.awaitTransaction {
// We manage only one filter for now
val filterBodyJson = filterBody.toJSONString()
val roomEventFilterJson = roomEventFilter.toJSONString()
val realm = Realm.getInstance(realmConfiguration)
val filterEntity = FilterEntity.getFilter(it)
val filter = FilterEntity.getFilter(realm)
if (filter.filterBodyJson != filterBody.toJSONString()) {
// Filter has changed, store it and reset the filter Id
realm.executeTransaction {
// We manage only one filter for now
val filterBodyJson = filterBody.toJSONString()
val roomEventFilterJson = roomEventFilter.toJSONString()
val filterEntity = FilterEntity.getFilter(it)
filterEntity.filterBodyJson = filterBodyJson
filterEntity.roomEventFilterJson = roomEventFilterJson
// Reset filterId
filterEntity.filterId = ""
filterEntity.filterBodyJson = filterBodyJson
filterEntity.roomEventFilterJson = roomEventFilterJson
// Reset filterId
filterEntity.filterId = ""
}
true
} else {
filter.filterId.isBlank()
}
result = true
} else {
result = filter.filterId.isBlank()
result
}
realm.close()
return result
}
override fun storeFilterId(filterBody: FilterBody, filterId: String) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
override suspend fun storeFilterId(filterBody: FilterBody, filterId: String) {
monarchy.awaitTransaction {
// We manage only one filter for now
val filterBodyJson = filterBody.toJSONString()
@ -73,39 +63,24 @@ internal class DefaultFilterRepository @Inject constructor(
?.findFirst()
?.filterId = filterId
}
realm.close()
}
override fun getFilter(): String {
val result: String
val realm = Realm.getInstance(realmConfiguration)
val filter = FilterEntity.getFilter(realm)
result = if (filter.filterId.isBlank()) {
// Use the Json format
filter.filterBodyJson
} else {
// Use FilterId
filter.filterId
override suspend fun getFilter(): String {
return Realm.getInstance(monarchy.realmConfiguration).use {
val filter = FilterEntity.getFilter(it)
if (filter.filterId.isBlank()) {
// Use the Json format
filter.filterBodyJson
} else {
// Use FilterId
filter.filterId
}
}
realm.close()
return result
}
override fun getRoomFilter(): String {
val realm = Realm.getInstance(realmConfiguration)
val filter = FilterEntity.getFilter(realm)
val result = filter.roomEventFilterJson
realm.close()
return result
override suspend fun getRoomFilter(): String {
return Realm.getInstance(monarchy.realmConfiguration).use {
FilterEntity.getFilter(it).roomEventFilterJson
}
}
}

View File

@ -21,36 +21,13 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject
internal class DefaultFilterService @Inject constructor(private val filterRepository: FilterRepository,
private val saveFilterTask: SaveFilterTask,
internal class DefaultFilterService @Inject constructor(private val saveFilterTask: SaveFilterTask,
private val taskExecutor: TaskExecutor) : FilterService {
// TODO Pass a list of support events instead
override fun setFilter(filterPreset: FilterService.FilterPreset) {
val filterBody = when (filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotFilterBody()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultFilterBody()
}
}
val roomFilter = when (filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotRoomFilter()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultRoomFilter()
}
}
val updated = filterRepository.storeFilter(filterBody, roomFilter)
if (updated) {
saveFilterTask
.configureWith(SaveFilterTask.Params(filterBody))
.executeBy(taskExecutor)
}
saveFilterTask
.configureWith(SaveFilterTask.Params(filterPreset))
.executeBy(taskExecutor)
}
}

View File

@ -16,18 +16,19 @@
package im.vector.matrix.android.internal.session.filter
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
/**
* Save a filter to the server
* Save a filter, in db and if any changes, upload to the server
*/
internal interface SaveFilterTask : Task<SaveFilterTask.Params, Unit> {
data class Params(
val filter: FilterBody
val filterPreset: FilterService.FilterPreset
)
}
@ -37,10 +38,29 @@ internal class DefaultSaveFilterTask @Inject constructor(@UserId private val use
) : SaveFilterTask {
override suspend fun execute(params: SaveFilterTask.Params) {
val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry
apiCall = filterAPI.uploadFilter(userId, params.filter)
val filterBody = when (params.filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotFilterBody()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultFilterBody()
}
}
val roomFilter = when (params.filterPreset) {
FilterService.FilterPreset.RiotFilter -> {
FilterFactory.createRiotRoomFilter()
}
FilterService.FilterPreset.NoFilter -> {
FilterFactory.createDefaultRoomFilter()
}
}
val updated = filterRepository.storeFilter(filterBody, roomFilter)
if (updated) {
val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry
apiCall = filterAPI.uploadFilter(userId, filterBody)
}
filterRepository.storeFilterId(filterBody, filterResponse.filterId)
}
filterRepository.storeFilterId(params.filter, filterResponse.filterId)
}
}

View File

@ -21,20 +21,20 @@ internal interface FilterRepository {
/**
* Return true if the filterBody has changed, or need to be sent to the server
*/
fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean
suspend fun storeFilter(filterBody: FilterBody, roomEventFilter: RoomEventFilter): Boolean
/**
* Set the filterId of this filter
*/
fun storeFilterId(filterBody: FilterBody, filterId: String)
suspend fun storeFilterId(filterBody: FilterBody, filterId: String)
/**
* Return filter json or filter id
*/
fun getFilter(): String
suspend fun getFilter(): String
/**
* Return the room filter
*/
fun getRoomFilter(): String
suspend fun getRoomFilter(): String
}

View File

@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.database.query.getOrCreate
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import java.util.*
import java.util.Date
import javax.inject.Inject
internal interface GetHomeServerCapabilitiesTask : Task<Unit, Unit>

View File

@ -28,7 +28,9 @@ import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.pushers.AddPushRuleTask
import im.vector.matrix.android.internal.session.pushers.GetPushRulesTask
import im.vector.matrix.android.internal.session.pushers.RemovePushRuleTask
import im.vector.matrix.android.internal.session.pushers.UpdatePushRuleEnableStatusTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -38,6 +40,8 @@ import javax.inject.Inject
@SessionScope
internal class DefaultPushRuleService @Inject constructor(private val getPushRulesTask: GetPushRulesTask,
private val updatePushRuleEnableStatusTask: UpdatePushRuleEnableStatusTask,
private val addPushRuleTask: AddPushRuleTask,
private val removePushRuleTask: RemovePushRuleTask,
private val taskExecutor: TaskExecutor,
private val monarchy: Monarchy
) : PushRuleService {
@ -98,6 +102,22 @@ internal class DefaultPushRuleService @Inject constructor(private val getPushRul
.executeBy(taskExecutor)
}
override fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
return addPushRuleTask
.configureWith(AddPushRuleTask.Params(kind, pushRule)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
return removePushRuleTask
.configureWith(RemovePushRuleTask.Params(kind, pushRule)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
synchronized(listeners) {
listeners.remove(listener)

View File

@ -0,0 +1,39 @@
/*
* 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.pushers
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface AddPushRuleTask : Task<AddPushRuleTask.Params, Unit> {
data class Params(
val kind: RuleKind,
val pushRule: PushRule
)
}
internal class DefaultAddPushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi)
: AddPushRuleTask {
override suspend fun execute(params: AddPushRuleTask.Params) {
return executeRequest {
apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule)
}
}
}

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.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import java.util.*
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject

View File

@ -25,6 +25,8 @@ import im.vector.matrix.android.api.session.pushers.PushersService
import im.vector.matrix.android.internal.session.notification.DefaultProcessEventForPushTask
import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService
import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask
import im.vector.matrix.android.internal.session.room.notification.DefaultSetRoomNotificationStateTask
import im.vector.matrix.android.internal.session.room.notification.SetRoomNotificationStateTask
import retrofit2.Retrofit
@Module
@ -67,6 +69,15 @@ internal abstract class PushersModule {
@Binds
abstract fun bindUpdatePushRuleEnableStatusTask(updatePushRuleEnableStatusTask: DefaultUpdatePushRuleEnableStatusTask): UpdatePushRuleEnableStatusTask
@Binds
abstract fun bindAddPushRuleTask(addPushRuleTask: DefaultAddPushRuleTask): AddPushRuleTask
@Binds
abstract fun bindRemovePushRuleTask(removePushRuleTask: DefaultRemovePushRuleTask): RemovePushRuleTask
@Binds
abstract fun bindSetRoomNotificationStateTask(setRoomNotificationStateTask: DefaultSetRoomNotificationStateTask): SetRoomNotificationStateTask
@Binds
abstract fun bindPushRuleService(pushRuleService: DefaultPushRuleService): PushRuleService

View File

@ -0,0 +1,39 @@
/*
* 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.pushers
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface RemovePushRuleTask : Task<RemovePushRuleTask.Params, Unit> {
data class Params(
val kind: RuleKind,
val pushRule: PushRule
)
}
internal class DefaultRemovePushRuleTask @Inject constructor(private val pushRulesApi: PushRulesApi)
: RemovePushRuleTask {
override suspend fun execute(params: RemovePushRuleTask.Params) {
return executeRequest {
apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId)
}
}
}

View File

@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.MembershipService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.relation.RelationService
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.session.room.reporting.ReportingService
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.api.session.room.send.DraftService
@ -49,7 +50,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
private val readService: ReadService,
private val cryptoService: CryptoService,
private val relationService: RelationService,
private val roomMembersService: MembershipService) :
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService) :
Room,
TimelineService by timelineService,
SendService by sendService,
@ -58,7 +60,8 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
ReportingService by reportingService,
ReadService by readService,
RelationService by relationService,
MembershipService by roomMembersService {
MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService {
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
val liveData = monarchy.findAllMappedWithChanges(

View File

@ -21,7 +21,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.room.model.RoomAvatarContent
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.prev
@ -41,8 +41,8 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
fun resolve(roomId: String): String? {
var res: String? = null
monarchy.doWithRealm { realm ->
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()?.asDomain()
res = roomName?.content.toModel<RoomAvatarContent>()?.avatarUrl
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()
res = ContentMapper.map(roomName?.content).toModel<RoomAvatarContent>()?.avatarUrl
if (!res.isNullOrEmpty()) {
return@doWithRealm
}
@ -60,6 +60,6 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona
}
private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
return ContentMapper.map(this?.content).toModel<RoomMember>()
}
}

View File

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService
import im.vector.matrix.android.internal.session.room.reporting.DefaultReportingService
@ -44,7 +45,8 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
private val reportingServiceFactory: DefaultReportingService.Factory,
private val readServiceFactory: DefaultReadService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory) :
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory) :
RoomFactory {
override fun create(roomId: String): Room {
@ -60,7 +62,8 @@ internal class DefaultRoomFactory @Inject constructor(private val monarchy: Mona
readServiceFactory.create(roomId),
cryptoService,
relationServiceFactory.create(roomId),
membershipServiceFactory.create(roomId)
membershipServiceFactory.create(roomId),
roomPushRuleServiceFactory.create(roomId)
)
}
}

View File

@ -21,7 +21,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.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
@ -65,8 +65,10 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
roomId: String,
membership: Membership? = null,
roomSummary: RoomSyncSummary? = null,
unreadNotifications: RoomSyncUnreadNotifications? = null) {
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
unreadNotifications: RoomSyncUnreadNotifications? = null,
updateMembers: Boolean = false) {
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
if (roomSummary != null) {
if (roomSummary.heroes.isNotEmpty()) {
@ -88,24 +90,27 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
}
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
// avoid this call if we are sure there are unread events
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, userId)
.findAll()
.asSequence()
.map { it.stateKey }
// avoid this call if we are sure there are unread events
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
if (updateMembers) {
val otherRoomMembers = RoomMembers(realm, roomId)
.queryRoomMembersEvent()
.notEqualTo(EventEntityFields.STATE_KEY, userId)
.findAll()
.asSequence()
.map { it.stateKey }
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers)
}
}
}

View File

@ -74,7 +74,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
it.updateSenderData()
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId)
roomSummaryUpdater.update(realm, roomId, updateMembers = true)
}
}

View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.R
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
@ -56,20 +56,20 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
var name: CharSequence? = null
monarchy.doWithRealm { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).prev()?.asDomain()
name = roomName?.content.toModel<RoomNameContent>()?.name
val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_NAME).prev()
name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name
if (!name.isNullOrEmpty()) {
return@doWithRealm
}
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()?.asDomain()
name = canonicalAlias?.content.toModel<RoomCanonicalAliasContent>()?.canonicalAlias
val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
name = ContentMapper.map(canonicalAlias?.content).toModel<RoomCanonicalAliasContent>()?.canonicalAlias
if (!name.isNullOrEmpty()) {
return@doWithRealm
}
val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()?.asDomain()
name = aliases?.content.toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
val aliases = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
name = ContentMapper.map(aliases?.content).toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
if (!name.isNullOrEmpty()) {
return@doWithRealm
}
@ -132,6 +132,6 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
}
private fun EventEntity?.toRoomMember(): RoomMember? {
return this?.asDomain()?.content?.toModel<RoomMember>()
return ContentMapper.map(this?.content).toModel<RoomMember>()
}
}

View File

@ -73,6 +73,7 @@ internal class RoomMembers(private val realm: Realm,
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING)
.isNotNull(EventEntityFields.STATE_KEY)
.distinct(EventEntityFields.STATE_KEY)
.isNotNull(EventEntityFields.CONTENT)
}

View File

@ -0,0 +1,72 @@
/*
* 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.notification
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.api.session.room.notification.RoomPushRuleService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor)
: RoomPushRuleService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): RoomPushRuleService
}
override fun getLiveRoomNotificationState(): LiveData<RoomNotificationState> {
return Transformations.map(getPushRuleForRoom()) {
it?.toRoomNotificationState() ?: RoomNotificationState.ALL_MESSAGES
}
}
override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable {
return setRoomNotificationStateTask
.configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
private fun getPushRuleForRoom(): LiveData<RoomPushRule?> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm ->
PushRuleEntity.where(realm, scope = RuleScope.GLOBAL, ruleId = roomId)
},
{ result ->
result.toRoomPushRule()
}
)
return Transformations.map(liveData) { results ->
results.firstOrNull()
}
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.notification
import im.vector.matrix.android.api.pushrules.RuleKind
import im.vector.matrix.android.api.pushrules.rest.PushRule
internal data class RoomPushRule(
val kind: RuleKind,
val rule: PushRule
)

View File

@ -0,0 +1,101 @@
/*
* 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.notification
import im.vector.matrix.android.api.pushrules.*
import im.vector.matrix.android.api.pushrules.rest.PushCondition
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.internal.database.mapper.PushRulesMapper
import im.vector.matrix.android.internal.database.model.PushRuleEntity
internal fun PushRuleEntity.toRoomPushRule(): RoomPushRule? {
val kind = parent?.firstOrNull()?.kind
val pushRule = when (kind) {
RuleSetKey.OVERRIDE -> {
PushRulesMapper.map(this)
}
RuleSetKey.ROOM -> {
PushRulesMapper.mapRoomRule(this)
}
else -> null
}
return if (pushRule == null || kind == null) {
null
} else {
RoomPushRule(kind, pushRule)
}
}
internal fun RoomNotificationState.toRoomPushRule(roomId: String): RoomPushRule? {
return when {
this == RoomNotificationState.ALL_MESSAGES -> null
this == RoomNotificationState.ALL_MESSAGES_NOISY -> {
val rule = PushRule(
actions = listOf(Action.Notify, Action.Sound()).toJson(),
enabled = true,
ruleId = roomId
)
return RoomPushRule(RuleSetKey.ROOM, rule)
}
else -> {
val condition = PushCondition(
kind = Condition.Kind.event_match.value,
key = "room_id",
pattern = roomId
)
val rule = PushRule(
actions = listOf(Action.DoNotNotify).toJson(),
enabled = true,
ruleId = roomId,
conditions = listOf(condition)
)
val kind = if (this == RoomNotificationState.MUTE) {
RuleSetKey.OVERRIDE
} else {
RuleSetKey.ROOM
}
return RoomPushRule(kind, rule)
}
}
}
internal fun RoomPushRule.toRoomNotificationState(): RoomNotificationState {
return if (rule.enabled) {
val actions = rule.getActions()
if (actions.contains(Action.DoNotNotify)) {
if (kind == RuleSetKey.OVERRIDE) {
RoomNotificationState.MUTE
} else {
RoomNotificationState.MENTIONS_ONLY
}
} else if (actions.contains(Action.Notify)) {
val hasSoundAction = actions.find {
it is Action.Sound
} != null
if (hasSoundAction) {
RoomNotificationState.ALL_MESSAGES_NOISY
} else {
RoomNotificationState.ALL_MESSAGES
}
} else {
RoomNotificationState.ALL_MESSAGES
}
} else {
RoomNotificationState.ALL_MESSAGES
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.notification
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.pushrules.RuleScope
import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
import im.vector.matrix.android.internal.database.model.PushRuleEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.pushers.AddPushRuleTask
import im.vector.matrix.android.internal.session.pushers.RemovePushRuleTask
import im.vector.matrix.android.internal.task.Task
import io.realm.Realm
import javax.inject.Inject
internal interface SetRoomNotificationStateTask : Task<SetRoomNotificationStateTask.Params, Unit> {
data class Params(
val roomId: String,
val roomNotificationState: RoomNotificationState
)
}
internal class DefaultSetRoomNotificationStateTask @Inject constructor(private val monarchy: Monarchy,
private val removePushRuleTask: RemovePushRuleTask,
private val addPushRuleTask: AddPushRuleTask)
: SetRoomNotificationStateTask {
override suspend fun execute(params: SetRoomNotificationStateTask.Params) {
val currentRoomPushRule = Realm.getInstance(monarchy.realmConfiguration).use {
PushRuleEntity.where(it, scope = RuleScope.GLOBAL, ruleId = params.roomId).findFirst()?.toRoomPushRule()
}
if (currentRoomPushRule != null) {
removePushRuleTask.execute(RemovePushRuleTask.Params(currentRoomPushRule.kind, currentRoomPushRule.rule))
}
val newRoomPushRule = params.roomNotificationState.toRoomPushRule(params.roomId)
if (newRoomPushRule != null) {
addPushRuleTask.execute(AddPushRuleTask.Params(newRoomPushRule.kind, newRoomPushRule.rule))
}
}
}

View File

@ -39,7 +39,6 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import java.util.*
import javax.inject.Inject
/**
@ -119,7 +118,7 @@ internal class LocalEchoEventFactory @Inject constructor(@UserId private val use
permalink,
stringProvider.getString(R.string.message_reply_to_prefix),
userLink,
originalEvent.senderName ?: originalEvent.root.senderId,
originalEvent.getDisambiguatedDisplayName(),
body.takeFormatted(),
createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted()
)

View File

@ -52,7 +52,8 @@ import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import timber.log.Timber
import java.util.*
import java.util.Collections
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import kotlin.collections.ArrayList

View File

@ -31,7 +31,7 @@ import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.SecureRandom
import java.util.*
import java.util.Calendar
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec

View File

@ -67,6 +67,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
}
suspend fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) {
Timber.v("Execute transaction from $this")
monarchy.awaitTransaction { realm ->
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter)
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter)
@ -133,6 +134,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomEntity.membership = Membership.JOIN
// State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
?: Int.MIN_VALUE
@ -146,7 +148,6 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
}
}
}
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(
realm,
@ -157,14 +158,19 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
)
roomEntity.addOrUpdate(chunkEntity)
}
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications)
val hasRoomMember = roomSync.state?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER
} != null || roomSync.timeline?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER
} != null
roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications, updateMembers = hasRoomMember)
return roomEntity
}
private fun handleInvitedRoom(realm: Realm,
roomId: String,
roomSync:
InvitedRoomSync): RoomEntity {
roomSync: InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE
@ -172,7 +178,10 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
roomEntity.addOrUpdate(chunkEntity)
}
roomSummaryUpdater.update(realm, roomId, Membership.INVITE)
val hasRoomMember = roomSync.inviteState?.events?.firstOrNull {
it.type == EventType.STATE_ROOM_MEMBER
} != null
roomSummaryUpdater.update(realm, roomId, Membership.INVITE, updateMembers = hasRoomMember)
return roomEntity
}

View File

@ -16,27 +16,24 @@
package im.vector.matrix.android.internal.session.sync
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.SyncEntity
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class SyncTokenStore @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
internal class SyncTokenStore @Inject constructor(private val monarchy: Monarchy) {
fun getLastToken(): String? {
val realm = Realm.getInstance(realmConfiguration)
val token = realm.where(SyncEntity::class.java).findFirst()?.nextBatch
realm.close()
return token
return Realm.getInstance(monarchy.realmConfiguration).use {
it.where(SyncEntity::class.java).findFirst()?.nextBatch
}
}
fun saveToken(token: String?) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
suspend fun saveToken(token: String?) {
monarchy.awaitTransaction {
val sync = SyncEntity(token)
it.insertOrUpdate(sync)
}
realm.close()
}
}

View File

@ -27,10 +27,9 @@ import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.pushers.SavePushRulesTask
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataPushRules
import im.vector.matrix.android.internal.session.sync.model.UserAccountDataSync
import im.vector.matrix.android.internal.session.sync.model.accountdata.*
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -44,6 +43,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val savePushRulesTask: SavePushRulesTask,
private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
private val taskExecutor: TaskExecutor) {
suspend fun handle(accountData: UserAccountDataSync?, invites: Map<String, InvitedRoomSync>?) {
@ -51,9 +51,18 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
when (it) {
is UserAccountDataDirectMessages -> handleDirectChatRooms(it)
is UserAccountDataPushRules -> handlePushRules(it)
else -> return@forEach
is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it)
is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}")
else -> error("Missing code here!")
}
}
// TODO Store all account data, app can be interested of it
// accountData?.list?.forEach {
// it.toString()
// MoshiProvider.providesMoshi()
// }
monarchy.doWithRealm { realm ->
synchronizeWithServerIfNeeded(realm, invites)
}
@ -114,4 +123,11 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc
updateUserAccountDataTask.configureWith(updateUserAccountParams).executeBy(taskExecutor)
}
}
private fun handleIgnoredUsers(userAccountDataIgnoredUsers: UserAccountDataIgnoredUsers) {
saveIgnoredUsersTask
.configureWith(SaveIgnoredUsersTask.Params(userAccountDataIgnoredUsers.content.ignoredUsers.keys.toList()))
.executeBy(taskExecutor)
// TODO If not initial sync, we should execute a init sync
}
}

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.sync.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync
// SyncResponse represents the request response for server sync v2.
@JsonClass(generateAdapter = true)

View File

@ -0,0 +1,39 @@
/*
* 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.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.emptyJsonDict
@JsonClass(generateAdapter = true)
internal data class IgnoredUsersContent(
/**
* Required. The map of users to ignore. UserId -> empty object for future enhancement
*/
@Json(name = "ignored_users") val ignoredUsers: Map<String, JsonDict>
) {
companion object {
fun createWithUserIds(userIds: List<String>): IgnoredUsersContent {
return IgnoredUsersContent(
ignoredUsers = userIds.associateWith { emptyJsonDict }
)
}
}
}

View File

@ -14,9 +14,13 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.sync.model
package im.vector.matrix.android.internal.session.sync.model.accountdata
internal interface UserAccountData {
import com.squareup.moshi.Json
internal abstract class UserAccountData {
@Json(name = "type") abstract val type: String
companion object {
const val TYPE_IGNORED_USER_LIST = "m.ignored_user_list"

View File

@ -14,12 +14,13 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.sync.model
package im.vector.matrix.android.internal.session.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserAccountDataDirectMessages(
@Json(name = "type") override val type: String = TYPE_DIRECT_MESSAGES,
@Json(name = "content") val content: Map<String, List<String>>
) : UserAccountData
) : UserAccountData()

View File

@ -14,12 +14,13 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.sync.model
package im.vector.matrix.android.internal.session.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserAccountDataFallback(
@Json(name = "type") override val type: String,
@Json(name = "content") val content: Map<String, Any>
) : UserAccountData
) : UserAccountData()

View File

@ -0,0 +1,26 @@
/*
* 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.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class UserAccountDataIgnoredUsers(
@Json(name = "type") override val type: String = TYPE_IGNORED_USER_LIST,
@Json(name = "content") val content: IgnoredUsersContent
) : UserAccountData()

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.sync.model
package im.vector.matrix.android.internal.session.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@ -22,5 +22,6 @@ import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
@JsonClass(generateAdapter = true)
internal data class UserAccountDataPushRules(
@Json(name = "type") override val type: String = TYPE_PUSH_RULES,
@Json(name = "content") val content: GetPushRulesResponse
) : UserAccountData
) : UserAccountData()

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.sync.model
package im.vector.matrix.android.internal.session.sync.model.accountdata
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View File

@ -29,9 +29,12 @@ import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.database.model.IgnoredUserEntityFields
import im.vector.matrix.android.internal.database.model.UserEntity
import im.vector.matrix.android.internal.database.model.UserEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -40,8 +43,8 @@ import javax.inject.Inject
internal class DefaultUserService @Inject constructor(private val monarchy: Monarchy,
private val searchUserTask: SearchUserTask,
private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask,
private val taskExecutor: TaskExecutor) : UserService {
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
monarchy.createDataSourceFactory { realm ->
realm.where(UserEntity::class.java)
@ -62,7 +65,7 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
override fun getUser(userId: String): User? {
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
?: return null
?: return null
return userEntity.asDomain()
}
@ -117,4 +120,33 @@ internal class DefaultUserService @Inject constructor(private val monarchy: Mona
}
.executeBy(taskExecutor)
}
override fun liveIgnoredUsers(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(IgnoredUserEntity::class.java)
.isNotEmpty(IgnoredUserEntityFields.USER_ID)
.sort(IgnoredUserEntityFields.USER_ID)
},
{ getUser(it.userId) ?: User(userId = it.userId) }
)
}
override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList())
return updateIgnoredUserIdsTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
val params = UpdateIgnoredUserIdsTask.Params(userIdsToUnIgnore = userIds.toList())
return updateIgnoredUserIdsTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
}
}

View File

@ -21,6 +21,10 @@ import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.user.accountdata.DefaultSaveIgnoredUsersTask
import im.vector.matrix.android.internal.session.user.accountdata.DefaultUpdateIgnoredUserIdsTask
import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask
import im.vector.matrix.android.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
import im.vector.matrix.android.internal.session.user.model.DefaultSearchUserTask
import im.vector.matrix.android.internal.session.user.model.SearchUserTask
import retrofit2.Retrofit
@ -43,4 +47,10 @@ internal abstract class UserModule {
@Binds
abstract fun bindSearchUserTask(searchUserTask: DefaultSearchUserTask): SearchUserTask
@Binds
abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask
@Binds
abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask
}

View File

@ -0,0 +1,46 @@
/*
* 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.user.accountdata
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject
/**
* Save the ignored users list in DB
*/
internal interface SaveIgnoredUsersTask : Task<SaveIgnoredUsersTask.Params, Unit> {
data class Params(
val userIds: List<String>
)
}
internal class DefaultSaveIgnoredUsersTask @Inject constructor(private val monarchy: Monarchy) : SaveIgnoredUsersTask {
override suspend fun execute(params: SaveIgnoredUsersTask.Params) {
monarchy.awaitTransaction { realm ->
// clear current ignored users
realm.where(IgnoredUserEntity::class.java)
.findAll()
.deleteAllFromRealm()
// And save the new received list
params.userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.user.accountdata
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.IgnoredUserEntity
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.sync.model.accountdata.IgnoredUsersContent
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
internal interface UpdateIgnoredUserIdsTask : Task<UpdateIgnoredUserIdsTask.Params, Unit> {
data class Params(
val userIdsToIgnore: List<String> = emptyList(),
val userIdsToUnIgnore: List<String> = emptyList()
)
}
internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(private val accountDataApi: AccountDataAPI,
private val monarchy: Monarchy,
private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
@UserId private val userId: String) : UpdateIgnoredUserIdsTask {
override suspend fun execute(params: UpdateIgnoredUserIdsTask.Params) {
// Get current list
val ignoredUserIds = monarchy.fetchAllMappedSync(
{ realm -> realm.where(IgnoredUserEntity::class.java) },
{ it.userId }
).toMutableSet()
val original = ignoredUserIds.toList()
ignoredUserIds.removeAll { it in params.userIdsToUnIgnore }
ignoredUserIds.addAll(params.userIdsToIgnore)
if (original == ignoredUserIds) {
// No change
return
}
val list = ignoredUserIds.toList()
val body = IgnoredUsersContent.createWithUserIds(list)
executeRequest<Unit> {
apiCall = accountDataApi.setAccountData(userId, UserAccountData.TYPE_IGNORED_USER_LIST, body)
}
// Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update)
saveIgnoredUsersTask.execute(SaveIgnoredUsersTask.Params(list))
}
}

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.sync.model.UserAccountData
import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject
@ -29,6 +29,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
fun getData(): Any
}
// TODO Use [UserAccountDataDirectMessages] class?
data class DirectChatParams(override val type: String = UserAccountData.TYPE_DIRECT_MESSAGES,
private val directMessages: Map<String, List<String>>
) : Params {

View File

@ -36,7 +36,7 @@ import java.security.*
import java.security.cert.CertificateException
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
import java.util.*
import java.util.Calendar
import java.util.zip.GZIPOutputStream
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.util
import im.vector.matrix.android.api.MatrixPatterns
import timber.log.Timber
import java.util.*
import java.util.Locale
/**
* Convert a string to an UTF8 String

View File

@ -172,4 +172,5 @@
<string name="event_status_sending_message">Изпращане на съобщение…</string>
<string name="clear_timeline_send_queue">Изчисти опашката за изпращане</string>
<string name="notice_room_third_party_revoked_invite">%1$s оттегли поканата за присъединяване на %2$s към стаята</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="verification_emoji_wrench">Spanner</string>
<string name="verification_emoji_airplane">Aeroplane</string>
</resources>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Note to translator: please add here only the string which are different than default version (which is en-rGB) -->
<string name="verification_emoji_wrench">Wrench</string>
<string name="verification_emoji_airplane">Airplane</string>
</resources>

View File

@ -12,8 +12,8 @@
<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_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>

View File

@ -170,7 +170,7 @@
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
<string name="verification_emoji_glasses">Glasses</string>
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
<string name="verification_emoji_wrench">Spanner</string>
<string name="verification_emoji_wrench">Wrench</string>
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
<string name="verification_emoji_santa">Santa</string>
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
@ -208,7 +208,7 @@
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
<string name="verification_emoji_bicycle">Bicycle</string>
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
<string name="verification_emoji_airplane">Aeroplane</string>
<string name="verification_emoji_airplane">Airplane</string>
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->
<string name="verification_emoji_rocket">Rocket</string>
<!-- All translations should be the same across all Riot clients, please use the same translation than RiotWeb -->

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.di.MoshiProvider
import org.junit.Assert
import org.junit.Assert.*
import org.junit.Test
class PushRuleActionsTest {
@ -63,22 +63,17 @@ class PushRuleActionsTest {
val pushRule = MoshiProvider.providesMoshi().adapter<PushRule>(PushRule::class.java).fromJson(rawPushRule)
Assert.assertNotNull("Should have parsed the rule", pushRule)
Assert.assertNotNull("Failed to parse actions", Action.mapFrom(pushRule!!))
assertNotNull("Should have parsed the rule", pushRule)
val actions = Action.mapFrom(pushRule)
Assert.assertEquals(3, actions!!.size)
val actions = pushRule!!.getActions()
assertEquals(3, actions.size)
Assert.assertEquals("First action should be notify", Action.Type.NOTIFY, actions[0].type)
assertTrue("First action should be notify", actions[0] is Action.Notify)
Assert.assertEquals("Second action should be tweak", Action.Type.SET_TWEAK, actions[1].type)
Assert.assertEquals("Second action tweak key should be sound", "sound", actions[1].tweak_action)
Assert.assertEquals("Second action should have default as stringValue", "default", actions[1].stringValue)
Assert.assertNull("Second action boolValue should be null", actions[1].boolValue)
assertTrue("Second action should be sound", actions[1] is Action.Sound)
assertEquals("Second action should have default sound", "default", (actions[1] as Action.Sound).sound)
Assert.assertEquals("Third action should be tweak", Action.Type.SET_TWEAK, actions[2].type)
Assert.assertEquals("Third action tweak key should be highlight", "highlight", actions[2].tweak_action)
Assert.assertEquals("Third action tweak param should be false", false, actions[2].boolValue)
Assert.assertNull("Third action stringValue should be null", actions[2].stringValue)
assertTrue("Third action should be highlight", actions[2] is Action.Highlight)
assertEquals("Third action tweak param should be false", false, (actions[2] as Action.Highlight).highlight)
}
}

View File

@ -16,23 +16,15 @@
package im.vector.matrix.android.api.pushrules
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert
import org.junit.Test
@ -133,17 +125,20 @@ class PushrulesConditionTest {
val conditionEqual3Bis = RoomMemberCountCondition("==3")
val conditionLessThan3 = RoomMemberCountCondition("<3")
val session = MockRoomService()
val room2JoinedId = "2joined"
val room3JoinedId = "3joined"
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = "2joined").also {
Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, session))
Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, session))
Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, session))
val roomStub2Joined = mockk<Room> {
every { getNumberOfJoinedMembers() } returns 2
}
val roomStub3Joined = mockk<Room> {
every { getNumberOfJoinedMembers() } returns 3
}
val sessionStub = mockk<RoomService> {
every { getRoom(room2JoinedId) } returns roomStub2Joined
every { getRoom(room3JoinedId) } returns roomStub3Joined
}
Event(
@ -151,10 +146,21 @@ class PushrulesConditionTest {
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = "3joined").also {
Assert.assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, session))
Assert.assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, session))
Assert.assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, session))
roomId = room2JoinedId).also {
Assert.assertFalse("This room does not have 3 members", conditionEqual3.isSatisfied(it, sessionStub))
Assert.assertFalse("This room does not have 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub))
Assert.assertTrue("This room has less than 3 members", conditionLessThan3.isSatisfied(it, sessionStub))
}
Event(
type = "m.room.message",
eventId = "mx0",
content = MessageTextContent("m.text", "A").toContent(),
originServerTs = 0,
roomId = room3JoinedId).also {
Assert.assertTrue("This room has 3 members", conditionEqual3.isSatisfied(it, sessionStub))
Assert.assertTrue("This room has 3 members", conditionEqual3Bis.isSatisfied(it, sessionStub))
Assert.assertFalse("This room has more than 3 members", conditionLessThan3.isSatisfied(it, sessionStub))
}
}
@ -171,208 +177,4 @@ class PushrulesConditionTest {
Assert.assertTrue("Notice", conditionEqual.isSatisfied(it))
}
}
class MockRoomService() : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun joinRoom(roomId: String, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getRoom(roomId: String): Room? {
return when (roomId) {
"2joined" -> MockRoom(roomId, 2)
"3joined" -> MockRoom(roomId, 3)
else -> null
}
}
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return MutableLiveData()
}
override fun markAllAsRead(roomIds: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
}
class MockRoom(override val roomId: String, val _numberOfJoinedMembers: Int) : Room {
override fun getReadMarkerLive(): LiveData<Optional<String>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getMyReadReceiptLive(): LiveData<Optional<String>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun resendMediaMessage(localEcho: TimelineEvent): Cancelable? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun deleteFailedEcho(localEcho: TimelineEvent) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun clearSendingQueue() {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun resendAllFailedMessages() {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun saveDraft(draft: UserDraft) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun deleteDraft() {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getDraftsLive(): LiveData<List<UserDraft>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getStateEvent(eventType: String): Event? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun editReply(replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, compatibilityBodyText: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
}
override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getNumberOfJoinedMembers(): Int {
return _numberOfJoinedMembers
}
override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun roomSummary(): RoomSummary? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendTextMessage(text: String, msgType: String, autoMarkdown: Boolean): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendFormattedTextMessage(text: String, formattedText: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendMedia(attachment: ContentAttachmentData): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun redactEvent(event: Event, reason: String?): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isEventRead(eventId: String): Boolean {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getRoomMember(userId: String): RoomMember? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getRoomMemberIdsLive(): LiveData<List<String>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun invite(userId: String, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun join(viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun leave(callback: MatrixCallback<Unit>): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun updateTopic(topic: String, callback: MatrixCallback<Unit>) {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun undoReaction(targetEventId: String, reaction: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun editTextMessage(targetEventId: String, msgType: String, newBodyText: String,
newBodyAutoMarkdown: Boolean, compatibilityBodyText: String): Cancelable {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun replyToMessage(eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Cancelable? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun getEventSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isEncrypted(): Boolean {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun encryptionAlgorithm(): String? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun shouldEncryptForInvitedMembers(): Boolean {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
}
}

View File

@ -32,7 +32,7 @@ cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-da/strings.xml ./mat
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-de/strings.xml ./matrix-sdk-android/src/main/res/values-de/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-el/strings.xml ./matrix-sdk-android/src/main/res/values-el/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-eo/strings.xml ./matrix-sdk-android/src/main/res/values-eo/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-en-rUS/strings.xml ./matrix-sdk-android/src/main/res/values-en-rUS/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-en-rGB/strings.xml ./matrix-sdk-android/src/main/res/values-en-rGB/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-es/strings.xml ./matrix-sdk-android/src/main/res/values-es/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-es-rMX/strings.xml ./matrix-sdk-android/src/main/res/values-es-rMX/strings.xml
cp ../matrix-android-sdk/matrix-sdk/src/main/res/values-eu/strings.xml ./matrix-sdk-android/src/main/res/values-eu/strings.xml
@ -102,6 +102,7 @@ cp ../riot-android/vector/src/main/res/values-ro/strings.xml ./vector/src
cp ../riot-android/vector/src/main/res/values-ru/strings.xml ./vector/src/main/res/values-ru/strings.xml
cp ../riot-android/vector/src/main/res/values-sk/strings.xml ./vector/src/main/res/values-sk/strings.xml
cp ../riot-android/vector/src/main/res/values-sq/strings.xml ./vector/src/main/res/values-sq/strings.xml
cp ../riot-android/vector/src/main/res/values-sr/strings.xml ./vector/src/main/res/values-sr/strings.xml
cp ../riot-android/vector/src/main/res/values-te/strings.xml ./vector/src/main/res/values-te/strings.xml
cp ../riot-android/vector/src/main/res/values-th/strings.xml ./vector/src/main/res/values-th/strings.xml
cp ../riot-android/vector/src/main/res/values-tlh/strings.xml ./vector/src/main/res/values-tlh/strings.xml

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