mirror of
https://github.com/element-hq/element-android
synced 2024-11-25 02:45:37 +03:00
Merge branch 'develop' into feature/initial_sync
This commit is contained in:
commit
2316c98a65
180 changed files with 5671 additions and 898 deletions
|
@ -18,6 +18,7 @@
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
<w>signin</w>
|
<w>signin</w>
|
||||||
|
<w>signout</w>
|
||||||
<w>signup</w>
|
<w>signup</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
|
|
32
CHANGES.md
32
CHANGES.md
|
@ -1,4 +1,28 @@
|
||||||
Changes in RiotX 0.10.0 (2019-XX-XX)
|
Changes in RiotX 0.11.0 (2019-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Implement soft logout (#281)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Handle navigation to room via room alias (#201)
|
||||||
|
- Open matrix.to link in RiotX (#57)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Use same default room colors than Riot-Web
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Scroll breadcrumbs to top when opened
|
||||||
|
- Render default room name when it starts with an emoji (#477)
|
||||||
|
- Do not display " (IRC)") in display names https://github.com/vector-im/riot-android/issues/444
|
||||||
|
|
||||||
|
Translations 🗣:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Include diff-match-patch sources as dependency
|
||||||
|
|
||||||
|
Changes in RiotX 0.10.0 (2019-12-10)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
@ -15,12 +39,6 @@ Bugfix 🐛:
|
||||||
- "ban" event are not rendered correctly (#716)
|
- "ban" event are not rendered correctly (#716)
|
||||||
- Fix crash when rotating screen in Room timeline
|
- Fix crash when rotating screen in Room timeline
|
||||||
|
|
||||||
Translations 🗣:
|
|
||||||
-
|
|
||||||
|
|
||||||
Build 🧱:
|
|
||||||
-
|
|
||||||
|
|
||||||
Changes in RiotX 0.9.1 (2019-12-05)
|
Changes in RiotX 0.9.1 (2019-12-05)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||||
classpath 'com.google.gms:google-services:4.3.2'
|
classpath 'com.google.gms:google-services:4.3.2'
|
||||||
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
@ -45,12 +45,6 @@ allprojects {
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven {
|
|
||||||
url 'https://repo.adobe.com/nexus/content/repositories/public/'
|
|
||||||
content {
|
|
||||||
includeGroupByRegex "diff_match_patch"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(JavaCompile).all {
|
tasks.withType(JavaCompile).all {
|
||||||
|
|
1
diff-match-patch/.gitignore
vendored
Normal file
1
diff-match-patch/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
8
diff-match-patch/build.gradle
Normal file
8
diff-match-patch/build.gradle
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = "8"
|
||||||
|
targetCompatibility = "8"
|
File diff suppressed because it is too large
Load diff
|
@ -1,38 +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 im.vector.matrix.android.api.MatrixCallback
|
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
|
||||||
import io.reactivex.CompletableEmitter
|
|
||||||
|
|
||||||
internal class MatrixCallbackCompletable<T>(private val completableEmitter: CompletableEmitter) : MatrixCallback<T> {
|
|
||||||
|
|
||||||
override fun onSuccess(data: T) {
|
|
||||||
completableEmitter.onComplete()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
completableEmitter.tryOnError(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Cancelable.toCompletable(completableEmitter: CompletableEmitter) {
|
|
||||||
completableEmitter.setCancellable {
|
|
||||||
this.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
fun <T> singleBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Single<T> = Single.create {
|
||||||
|
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
|
||||||
|
override fun onSuccess(data: T) {
|
||||||
|
it.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
it.tryOnError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cancelable = builder(callback)
|
||||||
|
it.setCancellable {
|
||||||
|
cancelable.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> completableBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Completable = Completable.create {
|
||||||
|
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
|
||||||
|
override fun onSuccess(data: T) {
|
||||||
|
it.onComplete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
it.tryOnError(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cancelable = builder(callback)
|
||||||
|
it.setCancellable {
|
||||||
|
cancelable.cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,13 +53,13 @@ class RxRoom(private val room: Room) {
|
||||||
return room.getMyReadReceiptLive().asObservable()
|
return room.getMyReadReceiptLive().asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
|
fun loadRoomMembersIfNeeded(): Single<Unit> = singleBuilder {
|
||||||
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
|
room.loadRoomMembersIfNeeded(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinRoom(reason: String? = null,
|
fun joinRoom(reason: String? = null,
|
||||||
viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||||
room.join(reason, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
room.join(reason, viaServers, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {
|
||||||
|
|
|
@ -66,20 +66,25 @@ class RxSession(private val session: Session) {
|
||||||
return session.livePagedUsers(filter).asObservable()
|
return session.livePagedUsers(filter).asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create {
|
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
|
||||||
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it)
|
session.createRoom(roomParams, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchUsersDirectory(search: String,
|
fun searchUsersDirectory(search: String,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
excludedUserIds: Set<String>): Single<List<User>> = Single.create {
|
excludedUserIds: Set<String>): Single<List<User>> = singleBuilder {
|
||||||
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it)
|
session.searchUsersDirectory(search, limit, excludedUserIds, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun joinRoom(roomId: String,
|
fun joinRoom(roomId: String,
|
||||||
reason: String? = null,
|
reason: String? = null,
|
||||||
viaServers: List<String> = emptyList()): Single<Unit> = Single.create {
|
viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
|
||||||
session.joinRoom(roomId, reason, viaServers, MatrixCallbackSingle(it)).toSingle(it)
|
session.joinRoom(roomId, reason, viaServers, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
|
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
|
||||||
|
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,5 +22,6 @@ package im.vector.matrix.android.api.auth.data
|
||||||
*/
|
*/
|
||||||
data class SessionParams(
|
data class SessionParams(
|
||||||
val credentials: Credentials,
|
val credentials: Credentials,
|
||||||
val homeServerConnectionConfig: HomeServerConnectionConfig
|
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
val isTokenValid: Boolean
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,4 +45,4 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
abstract class FeatureFailure : Failure()
|
abstract class FeatureFailure : Failure()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Throwable.isTokenError() = this is Failure.ServerError && (this.error.code == MatrixError.UNKNOWN_TOKEN || this.error.code == MatrixError.MISSING_TOKEN)
|
fun Throwable.isTokenError() = this is Failure.ServerError && (this.error.code == MatrixError.M_UNKNOWN_TOKEN || this.error.code == MatrixError.M_MISSING_TOKEN)
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.failure
|
package im.vector.matrix.android.api.failure
|
||||||
|
|
||||||
// This data class will be sent to the bus
|
// This class will be sent to the bus
|
||||||
data class ConsentNotGivenError(
|
sealed class GlobalError {
|
||||||
val consentUri: String
|
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||||
)
|
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||||
|
}
|
|
@ -22,45 +22,112 @@ import com.squareup.moshi.JsonClass
|
||||||
/**
|
/**
|
||||||
* This data class holds the error defined by the matrix specifications.
|
* This data class holds the error defined by the matrix specifications.
|
||||||
* You shouldn't have to instantiate it.
|
* You shouldn't have to instantiate it.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#api-standards
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MatrixError(
|
data class MatrixError(
|
||||||
|
/** unique string which can be used to handle an error message */
|
||||||
@Json(name = "errcode") val code: String,
|
@Json(name = "errcode") val code: String,
|
||||||
|
/** human-readable error message */
|
||||||
@Json(name = "error") val message: String,
|
@Json(name = "error") val message: String,
|
||||||
|
|
||||||
|
// For M_CONSENT_NOT_GIVEN
|
||||||
@Json(name = "consent_uri") val consentUri: String? = null,
|
@Json(name = "consent_uri") val consentUri: String? = null,
|
||||||
// RESOURCE_LIMIT_EXCEEDED data
|
// For M_RESOURCE_LIMIT_EXCEEDED
|
||||||
@Json(name = "limit_type") val limitType: String? = null,
|
@Json(name = "limit_type") val limitType: String? = null,
|
||||||
@Json(name = "admin_contact") val adminUri: String? = null,
|
@Json(name = "admin_contact") val adminUri: String? = null,
|
||||||
// For LIMIT_EXCEEDED
|
// For M_LIMIT_EXCEEDED
|
||||||
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null) {
|
@Json(name = "retry_after_ms") val retryAfterMillis: Long? = null,
|
||||||
|
// For M_UNKNOWN_TOKEN
|
||||||
|
@Json(name = "soft_logout") val isSoftLogout: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val FORBIDDEN = "M_FORBIDDEN"
|
/** Forbidden access, e.g. joining a room without permission, failed login. */
|
||||||
const val UNKNOWN = "M_UNKNOWN"
|
const val M_FORBIDDEN = "M_FORBIDDEN"
|
||||||
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
/** An unknown error has occurred. */
|
||||||
const val MISSING_TOKEN = "M_MISSING_TOKEN"
|
const val M_UNKNOWN = "M_UNKNOWN"
|
||||||
const val BAD_JSON = "M_BAD_JSON"
|
/** The access token specified was not recognised. */
|
||||||
const val NOT_JSON = "M_NOT_JSON"
|
const val M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||||
const val NOT_FOUND = "M_NOT_FOUND"
|
/** No access token was specified for the request. */
|
||||||
const val LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
const val M_MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||||
const val USER_IN_USE = "M_USER_IN_USE"
|
/** Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys. */
|
||||||
const val ROOM_IN_USE = "M_ROOM_IN_USE"
|
const val M_BAD_JSON = "M_BAD_JSON"
|
||||||
const val BAD_PAGINATION = "M_BAD_PAGINATION"
|
/** Request did not contain valid JSON. */
|
||||||
const val UNAUTHORIZED = "M_UNAUTHORIZED"
|
const val M_NOT_JSON = "M_NOT_JSON"
|
||||||
const val OLD_VERSION = "M_OLD_VERSION"
|
/** No resource was found for this request. */
|
||||||
const val UNRECOGNIZED = "M_UNRECOGNIZED"
|
const val M_NOT_FOUND = "M_NOT_FOUND"
|
||||||
|
/** Too many requests have been sent in a short period of time. Wait a while then try again. */
|
||||||
|
const val M_LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||||
|
|
||||||
const val LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
/* ==========================================================================================
|
||||||
const val THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
* Other error codes the client might encounter are
|
||||||
// Error code returned by the server when no account matches the given 3pid
|
* ========================================================================================== */
|
||||||
const val THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
|
||||||
const val THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
/** Encountered when trying to register a user ID which has been taken. */
|
||||||
const val SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
const val M_USER_IN_USE = "M_USER_IN_USE"
|
||||||
const val TOO_LARGE = "M_TOO_LARGE"
|
/** Sent when the room alias given to the createRoom API is already in use. */
|
||||||
|
const val M_ROOM_IN_USE = "M_ROOM_IN_USE"
|
||||||
|
/** (Not documented yet) */
|
||||||
|
const val M_BAD_PAGINATION = "M_BAD_PAGINATION"
|
||||||
|
/** The request was not correctly authorized. Usually due to login failures. */
|
||||||
|
const val M_UNAUTHORIZED = "M_UNAUTHORIZED"
|
||||||
|
/** (Not documented yet) */
|
||||||
|
const val M_OLD_VERSION = "M_OLD_VERSION"
|
||||||
|
/** The server did not understand the request. */
|
||||||
|
const val M_UNRECOGNIZED = "M_UNRECOGNIZED"
|
||||||
|
/** (Not documented yet) */
|
||||||
|
const val M_LOGIN_EMAIL_URL_NOT_YET = "M_LOGIN_EMAIL_URL_NOT_YET"
|
||||||
|
/** Authentication could not be performed on the third party identifier. */
|
||||||
|
const val M_THREEPID_AUTH_FAILED = "M_THREEPID_AUTH_FAILED"
|
||||||
|
/** Sent when a threepid given to an API cannot be used because no record matching the threepid was found. */
|
||||||
|
const val M_THREEPID_NOT_FOUND = "M_THREEPID_NOT_FOUND"
|
||||||
|
/** Sent when a threepid given to an API cannot be used because the same threepid is already in use. */
|
||||||
|
const val M_THREEPID_IN_USE = "M_THREEPID_IN_USE"
|
||||||
|
/** The client's request used a third party server, eg. identity server, that this server does not trust. */
|
||||||
|
const val M_SERVER_NOT_TRUSTED = "M_SERVER_NOT_TRUSTED"
|
||||||
|
/** The request or entity was too large. */
|
||||||
|
const val M_TOO_LARGE = "M_TOO_LARGE"
|
||||||
|
/** (Not documented yet) */
|
||||||
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
const val M_CONSENT_NOT_GIVEN = "M_CONSENT_NOT_GIVEN"
|
||||||
const val RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
/** The request cannot be completed because the homeserver has reached a resource limit imposed on it. For example,
|
||||||
const val WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
* a homeserver held in a shared hosting environment may reach a resource limit if it starts using too much memory
|
||||||
|
* or disk space. The error MUST have an admin_contact field to provide the user receiving the error a place to reach
|
||||||
|
* out to. Typically, this error will appear on routes which attempt to modify state (eg: sending messages, account
|
||||||
|
* data, etc) and not routes which only read state (eg: /sync, get account data, etc). */
|
||||||
|
const val M_RESOURCE_LIMIT_EXCEEDED = "M_RESOURCE_LIMIT_EXCEEDED"
|
||||||
|
/** The user ID associated with the request has been deactivated. Typically for endpoints that prove authentication, such as /login. */
|
||||||
|
const val M_USER_DEACTIVATED = "M_USER_DEACTIVATED"
|
||||||
|
/** Encountered when trying to register a user ID which is not valid. */
|
||||||
|
const val M_INVALID_USERNAME = "M_INVALID_USERNAME"
|
||||||
|
/** Sent when the initial state given to the createRoom API is invalid. */
|
||||||
|
const val M_INVALID_ROOM_STATE = "M_INVALID_ROOM_STATE"
|
||||||
|
/** The server does not permit this third party identifier. This may happen if the server only permits,
|
||||||
|
* for example, email addresses from a particular domain. */
|
||||||
|
const val M_THREEPID_DENIED = "M_THREEPID_DENIED"
|
||||||
|
/** The client's request to create a room used a room version that the server does not support. */
|
||||||
|
const val M_UNSUPPORTED_ROOM_VERSION = "M_UNSUPPORTED_ROOM_VERSION"
|
||||||
|
/** The client attempted to join a room that has a version the server does not support.
|
||||||
|
* Inspect the room_version property of the error response for the room's version. */
|
||||||
|
const val M_INCOMPATIBLE_ROOM_VERSION = "M_INCOMPATIBLE_ROOM_VERSION"
|
||||||
|
/** The state change requested cannot be performed, such as attempting to unban a user who is not banned. */
|
||||||
|
const val M_BAD_STATE = "M_BAD_STATE"
|
||||||
|
/** The room or resource does not permit guests to access it. */
|
||||||
|
const val M_GUEST_ACCESS_FORBIDDEN = "M_GUEST_ACCESS_FORBIDDEN"
|
||||||
|
/** A Captcha is required to complete the request. */
|
||||||
|
const val M_CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||||
|
/** The Captcha provided did not match what was expected. */
|
||||||
|
const val M_CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||||
|
/** A required parameter was missing from the request. */
|
||||||
|
const val M_MISSING_PARAM = "M_MISSING_PARAM"
|
||||||
|
/** A parameter that was specified has the wrong value. For example, the server expected an integer and instead received a string. */
|
||||||
|
const val M_INVALID_PARAM = "M_INVALID_PARAM"
|
||||||
|
/** The resource being requested is reserved by an application service, or the application service making the request has not created the resource. */
|
||||||
|
const val M_EXCLUSIVE = "M_EXCLUSIVE"
|
||||||
|
/** The user is unable to reject an invite to join the server notices room. See the Server Notices module for more information. */
|
||||||
|
const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||||
|
/** (Not documented yet) */
|
||||||
|
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||||
|
|
||||||
// Possible value for "limit_type"
|
// Possible value for "limit_type"
|
||||||
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
const val LIMIT_TYPE_MAU = "monthly_active_user"
|
||||||
|
|
|
@ -24,9 +24,7 @@ import android.net.Uri
|
||||||
*/
|
*/
|
||||||
sealed class PermalinkData {
|
sealed class PermalinkData {
|
||||||
|
|
||||||
data class EventLink(val roomIdOrAlias: String, val eventId: String) : PermalinkData()
|
data class RoomLink(val roomIdOrAlias: String, val isRoomAlias: Boolean, val eventId: String?) : PermalinkData()
|
||||||
|
|
||||||
data class RoomLink(val roomIdOrAlias: String) : PermalinkData()
|
|
||||||
|
|
||||||
data class UserLink(val userId: String) : PermalinkData()
|
data class UserLink(val userId: String) : PermalinkData()
|
||||||
|
|
||||||
|
|
|
@ -60,16 +60,21 @@ object PermalinkParser {
|
||||||
return PermalinkData.FallbackLink(uri)
|
return PermalinkData.FallbackLink(uri)
|
||||||
}
|
}
|
||||||
return when {
|
return when {
|
||||||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||||
MatrixPatterns.isRoomId(identifier) -> {
|
MatrixPatterns.isRoomId(identifier) -> {
|
||||||
if (!extraParameter.isNullOrEmpty() && MatrixPatterns.isEventId(extraParameter)) {
|
val eventId = extraParameter.takeIf {
|
||||||
PermalinkData.EventLink(roomIdOrAlias = identifier, eventId = extraParameter)
|
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
|
||||||
} else {
|
|
||||||
PermalinkData.RoomLink(roomIdOrAlias = identifier)
|
|
||||||
}
|
}
|
||||||
|
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false, eventId = eventId)
|
||||||
}
|
}
|
||||||
else -> PermalinkData.FallbackLink(uri)
|
MatrixPatterns.isRoomAlias(identifier) -> {
|
||||||
|
val eventId = extraParameter.takeIf {
|
||||||
|
!it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
|
||||||
|
}
|
||||||
|
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true, eventId = eventId)
|
||||||
|
}
|
||||||
|
else -> PermalinkData.FallbackLink(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.session
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||||
import im.vector.matrix.android.api.session.cache.CacheService
|
import im.vector.matrix.android.api.session.cache.CacheService
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
|
@ -62,6 +62,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
val sessionParams: SessionParams
|
val sessionParams: SessionParams
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The session is valid, i.e. it has a valid token so far
|
||||||
|
*/
|
||||||
|
val isOpenable: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful shortcut to get access to the userId
|
* Useful shortcut to get access to the userId
|
||||||
*/
|
*/
|
||||||
|
@ -81,7 +86,7 @@ interface Session :
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launches infinite periodic background syncs
|
* Launches infinite periodic background syncs
|
||||||
* THis does not work in doze mode :/
|
* This does not work in doze mode :/
|
||||||
* If battery optimization is on it can work in app standby but that's all :/
|
* If battery optimization is on it can work in app standby but that's all :/
|
||||||
*/
|
*/
|
||||||
fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L)
|
fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L)
|
||||||
|
@ -141,13 +146,10 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
interface Listener {
|
interface Listener {
|
||||||
/**
|
/**
|
||||||
* The access token is not valid anymore
|
* Possible cases:
|
||||||
|
* - The access token is not valid anymore,
|
||||||
|
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
|
||||||
*/
|
*/
|
||||||
fun onInvalidToken()
|
fun onGlobalError(globalError: GlobalError)
|
||||||
|
|
||||||
/**
|
|
||||||
* A M_CONSENT_NOT_GIVEN error has been received from the homeserver
|
|
||||||
*/
|
|
||||||
fun onConsentNotGivenError(consentNotGivenError: ConsentNotGivenError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||||
|
@ -74,4 +75,11 @@ interface RoomService {
|
||||||
*/
|
*/
|
||||||
fun markAllAsRead(roomIds: List<String>,
|
fun markAllAsRead(roomIds: List<String>,
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a room alias to a room ID.
|
||||||
|
*/
|
||||||
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
|
searchOnServer: Boolean,
|
||||||
|
callback: MatrixCallback<Optional<String>>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,6 @@ interface MembershipService {
|
||||||
/**
|
/**
|
||||||
* Join the room, or accept an invitation.
|
* Join the room, or accept an invitation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fun join(reason: String? = null,
|
fun join(reason: String? = null,
|
||||||
viaServers: List<String> = emptyList(),
|
viaServers: List<String> = emptyList(),
|
||||||
callback: MatrixCallback<Unit>): Cancelable
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
|
@ -29,6 +29,8 @@ data class RoomSummary(
|
||||||
val displayName: String = "",
|
val displayName: String = "",
|
||||||
val topic: String = "",
|
val topic: String = "",
|
||||||
val avatarUrl: String = "",
|
val avatarUrl: String = "",
|
||||||
|
val canonicalAlias: String? = null,
|
||||||
|
val aliases: List<String> = emptyList(),
|
||||||
val isDirect: Boolean = false,
|
val isDirect: Boolean = false,
|
||||||
val latestPreviewableEvent: TimelineEvent? = null,
|
val latestPreviewableEvent: TimelineEvent? = null,
|
||||||
val otherMemberIds: List<String> = emptyList(),
|
val otherMemberIds: List<String> = emptyList(),
|
||||||
|
|
|
@ -16,11 +16,12 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.room.send
|
package im.vector.matrix.android.api.session.room.send
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag class for spans that should mention a user.
|
* Tag class for spans that should mention a user.
|
||||||
* These Spans will be transformed into pills when detected in message to send
|
* These Spans will be transformed into pills when detected in message to send
|
||||||
*/
|
*/
|
||||||
interface UserMentionSpan {
|
interface UserMentionSpan {
|
||||||
val displayName: String
|
val matrixItem: MatrixItem
|
||||||
val userId: String
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,31 @@
|
||||||
package im.vector.matrix.android.api.session.signout
|
package im.vector.matrix.android.api.session.signout
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines a method to sign out. It's implemented at the session level.
|
* This interface defines a method to sign out, or to renew the token. It's implemented at the session level.
|
||||||
*/
|
*/
|
||||||
interface SignOutService {
|
interface SignOutService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out
|
* Ask the homeserver for a new access token.
|
||||||
|
* The same deviceId will be used
|
||||||
*/
|
*/
|
||||||
fun signOut(callback: MatrixCallback<Unit>)
|
fun signInAgain(password: String,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the session with credentials received after SSO
|
||||||
|
*/
|
||||||
|
fun updateCredentials(credentials: Credentials,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign out, and release the session, clear all the session data, including crypto data
|
||||||
|
* @param sigOutFromHomeserver true if the sign out request has to be done
|
||||||
|
*/
|
||||||
|
fun signOut(sigOutFromHomeserver: Boolean,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,11 @@
|
||||||
package im.vector.matrix.android.api.session.sync
|
package im.vector.matrix.android.api.session.sync
|
||||||
|
|
||||||
sealed class SyncState {
|
sealed class SyncState {
|
||||||
object IDLE : SyncState()
|
object Idle : SyncState()
|
||||||
data class RUNNING(val afterPause: Boolean) : SyncState()
|
data class Running(val afterPause: Boolean) : SyncState()
|
||||||
object PAUSED : SyncState()
|
object Paused : SyncState()
|
||||||
object KILLING : SyncState()
|
object Killing : SyncState()
|
||||||
object KILLED : SyncState()
|
object Killed : SyncState()
|
||||||
object NO_NETWORK : SyncState()
|
object NoNetwork : SyncState()
|
||||||
|
object InvalidToken : SyncState()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import im.vector.matrix.android.BuildConfig
|
||||||
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
|
||||||
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
sealed class MatrixItem(
|
||||||
|
open val id: String,
|
||||||
|
open val displayName: String?,
|
||||||
|
open val avatarUrl: String?
|
||||||
|
) {
|
||||||
|
data class UserItem(override val id: String,
|
||||||
|
override val displayName: String? = null,
|
||||||
|
override val avatarUrl: String? = null)
|
||||||
|
: MatrixItem(id, displayName?.removeSuffix(ircPattern), avatarUrl) {
|
||||||
|
init {
|
||||||
|
if (BuildConfig.DEBUG) checkId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class EventItem(override val id: String,
|
||||||
|
override val displayName: String? = null,
|
||||||
|
override val avatarUrl: String? = null)
|
||||||
|
: MatrixItem(id, displayName, avatarUrl) {
|
||||||
|
init {
|
||||||
|
if (BuildConfig.DEBUG) checkId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RoomItem(override val id: String,
|
||||||
|
override val displayName: String? = null,
|
||||||
|
override val avatarUrl: String? = null)
|
||||||
|
: MatrixItem(id, displayName, avatarUrl) {
|
||||||
|
init {
|
||||||
|
if (BuildConfig.DEBUG) checkId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RoomAliasItem(override val id: String,
|
||||||
|
override val displayName: String? = null,
|
||||||
|
override val avatarUrl: String? = null)
|
||||||
|
: MatrixItem(id, displayName, avatarUrl) {
|
||||||
|
init {
|
||||||
|
if (BuildConfig.DEBUG) checkId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GroupItem(override val id: String,
|
||||||
|
override val displayName: String? = null,
|
||||||
|
override val avatarUrl: String? = null)
|
||||||
|
: MatrixItem(id, displayName, avatarUrl) {
|
||||||
|
init {
|
||||||
|
if (BuildConfig.DEBUG) checkId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBestName(): String {
|
||||||
|
return displayName?.takeIf { it.isNotBlank() } ?: id
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun checkId() {
|
||||||
|
if (!id.startsWith(getIdPrefix())) {
|
||||||
|
error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the prefix as defined in the matrix spec (and not extracted from the id)
|
||||||
|
*/
|
||||||
|
fun getIdPrefix() = when (this) {
|
||||||
|
is UserItem -> '@'
|
||||||
|
is EventItem -> '$'
|
||||||
|
is RoomItem -> '!'
|
||||||
|
is RoomAliasItem -> '#'
|
||||||
|
is GroupItem -> '+'
|
||||||
|
}
|
||||||
|
|
||||||
|
fun firstLetterOfDisplayName(): String {
|
||||||
|
return getBestName()
|
||||||
|
.let { dn ->
|
||||||
|
var startIndex = 0
|
||||||
|
val initial = dn[startIndex]
|
||||||
|
|
||||||
|
if (initial in listOf('@', '#', '+') && dn.length > 1) {
|
||||||
|
startIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = 1
|
||||||
|
var first = dn[startIndex]
|
||||||
|
|
||||||
|
// LEFT-TO-RIGHT MARK
|
||||||
|
if (dn.length >= 2 && 0x200e == first.toInt()) {
|
||||||
|
startIndex++
|
||||||
|
first = dn[startIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it’s the start of a surrogate pair
|
||||||
|
if (first.toInt() in 0xD800..0xDBFF && dn.length > startIndex + 1) {
|
||||||
|
val second = dn[startIndex + 1]
|
||||||
|
if (second.toInt() in 0xDC00..0xDFFF) {
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dn.substring(startIndex, startIndex + length)
|
||||||
|
}
|
||||||
|
.toUpperCase(Locale.ROOT)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ircPattern = " (IRC)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Extensions to create MatrixItem
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
|
fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl)
|
||||||
|
fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl)
|
||||||
|
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl)
|
|
@ -53,7 +53,7 @@ internal abstract class AuthModule {
|
||||||
.name("matrix-sdk-auth.realm")
|
.name("matrix-sdk-auth.realm")
|
||||||
.modules(AuthRealmModule())
|
.modules(AuthRealmModule())
|
||||||
.schemaVersion(AuthRealmMigration.SCHEMA_VERSION)
|
.schemaVersion(AuthRealmMigration.SCHEMA_VERSION)
|
||||||
.migration(AuthRealmMigration())
|
.migration(AuthRealmMigration)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,8 @@ internal class DefaultSessionCreator @Inject constructor(
|
||||||
?.also { Timber.d("Overriding identity server url to $it") }
|
?.also { Timber.d("Overriding identity server url to $it") }
|
||||||
?.let { Uri.parse(it) }
|
?.let { Uri.parse(it) }
|
||||||
?: homeServerConnectionConfig.identityServerUri
|
?: homeServerConnectionConfig.identityServerUri
|
||||||
))
|
),
|
||||||
|
isTokenValid = true)
|
||||||
|
|
||||||
sessionParamsStore.save(sessionParams)
|
sessionParamsStore.save(sessionParams)
|
||||||
return sessionManager.getOrCreateSession(sessionParams)
|
return sessionManager.getOrCreateSession(sessionParams)
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth
|
package im.vector.matrix.android.internal.auth
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
|
||||||
internal interface SessionParamsStore {
|
internal interface SessionParamsStore {
|
||||||
|
@ -28,6 +29,10 @@ internal interface SessionParamsStore {
|
||||||
|
|
||||||
suspend fun save(sessionParams: SessionParams)
|
suspend fun save(sessionParams: SessionParams)
|
||||||
|
|
||||||
|
suspend fun setTokenInvalid(userId: String)
|
||||||
|
|
||||||
|
suspend fun updateCredentials(newCredentials: Credentials)
|
||||||
|
|
||||||
suspend fun delete(userId: String)
|
suspend fun delete(userId: String)
|
||||||
|
|
||||||
suspend fun deleteAll()
|
suspend fun deleteAll()
|
||||||
|
|
|
@ -20,12 +20,10 @@ import io.realm.DynamicRealm
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class AuthRealmMigration : RealmMigration {
|
internal object AuthRealmMigration : RealmMigration {
|
||||||
|
|
||||||
companion object {
|
// Current schema version
|
||||||
// Current schema version
|
const val SCHEMA_VERSION = 2L
|
||||||
const val SCHEMA_VERSION = 1L
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||||
|
@ -46,5 +44,14 @@ internal class AuthRealmMigration : RealmMigration {
|
||||||
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
|
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
|
||||||
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
|
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion <= 1) {
|
||||||
|
Timber.d("Step 1 -> 2")
|
||||||
|
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
|
||||||
|
|
||||||
|
realm.schema.get("SessionParamsEntity")
|
||||||
|
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
|
||||||
|
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.auth.db
|
package im.vector.matrix.android.internal.auth.db
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.database.awaitTransaction
|
import im.vector.matrix.android.internal.database.awaitTransaction
|
||||||
|
@ -75,6 +76,53 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun setTokenInvalid(userId: String) {
|
||||||
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
|
val currentSessionParams = realm
|
||||||
|
.where(SessionParamsEntity::class.java)
|
||||||
|
.equalTo(SessionParamsEntityFields.USER_ID, userId)
|
||||||
|
.findAll()
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
if (currentSessionParams == null) {
|
||||||
|
// Should not happen
|
||||||
|
"Session param not found for user $userId"
|
||||||
|
.let { Timber.w(it) }
|
||||||
|
.also { error(it) }
|
||||||
|
} else {
|
||||||
|
currentSessionParams.isTokenValid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateCredentials(newCredentials: Credentials) {
|
||||||
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
|
val currentSessionParams = realm
|
||||||
|
.where(SessionParamsEntity::class.java)
|
||||||
|
.equalTo(SessionParamsEntityFields.USER_ID, newCredentials.userId)
|
||||||
|
.findAll()
|
||||||
|
.map { mapper.map(it) }
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
if (currentSessionParams == null) {
|
||||||
|
// Should not happen
|
||||||
|
"Session param not found for user ${newCredentials.userId}"
|
||||||
|
.let { Timber.w(it) }
|
||||||
|
.also { error(it) }
|
||||||
|
} else {
|
||||||
|
val newSessionParams = currentSessionParams.copy(
|
||||||
|
credentials = newCredentials,
|
||||||
|
isTokenValid = true
|
||||||
|
)
|
||||||
|
|
||||||
|
val entity = mapper.map(newSessionParams)
|
||||||
|
if (entity != null) {
|
||||||
|
realm.insertOrUpdate(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun delete(userId: String) {
|
override suspend fun delete(userId: String) {
|
||||||
awaitTransaction(realmConfiguration) {
|
awaitTransaction(realmConfiguration) {
|
||||||
it.where(SessionParamsEntity::class.java)
|
it.where(SessionParamsEntity::class.java)
|
||||||
|
|
|
@ -22,5 +22,8 @@ import io.realm.annotations.PrimaryKey
|
||||||
internal open class SessionParamsEntity(
|
internal open class SessionParamsEntity(
|
||||||
@PrimaryKey var userId: String = "",
|
@PrimaryKey var userId: String = "",
|
||||||
var credentialsJson: String = "",
|
var credentialsJson: String = "",
|
||||||
var homeServerConnectionConfigJson: String = ""
|
var homeServerConnectionConfigJson: String = "",
|
||||||
|
// Set to false when the token is invalid and the user has been soft logged out
|
||||||
|
// In case of hard logout, this object is deleted from DB
|
||||||
|
var isTokenValid: Boolean = true
|
||||||
) : RealmObject()
|
) : RealmObject()
|
||||||
|
|
|
@ -36,7 +36,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
if (credentials == null || homeServerConnectionConfig == null) {
|
if (credentials == null || homeServerConnectionConfig == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return SessionParams(credentials, homeServerConnectionConfig)
|
return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun map(sessionParams: SessionParams?): SessionParamsEntity? {
|
fun map(sessionParams: SessionParams?): SessionParamsEntity? {
|
||||||
|
@ -48,6 +48,10 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
|
||||||
if (credentialsJson == null || homeServerConnectionConfigJson == null) {
|
if (credentialsJson == null || homeServerConnectionConfigJson == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return SessionParamsEntity(sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson)
|
return SessionParamsEntity(
|
||||||
|
sessionParams.credentials.userId,
|
||||||
|
credentialsJson,
|
||||||
|
homeServerConnectionConfigJson,
|
||||||
|
sessionParams.isTokenValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -807,7 +807,7 @@ internal class KeysBackup @Inject constructor(
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
if (failure is Failure.ServerError
|
if (failure is Failure.ServerError
|
||||||
&& failure.error.code == MatrixError.NOT_FOUND) {
|
&& failure.error.code == MatrixError.M_NOT_FOUND) {
|
||||||
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
|
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
|
||||||
callback.onSuccess(null)
|
callback.onSuccess(null)
|
||||||
} else {
|
} else {
|
||||||
|
@ -830,7 +830,7 @@ internal class KeysBackup @Inject constructor(
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
if (failure is Failure.ServerError
|
if (failure is Failure.ServerError
|
||||||
&& failure.error.code == MatrixError.NOT_FOUND) {
|
&& failure.error.code == MatrixError.M_NOT_FOUND) {
|
||||||
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
|
// Workaround because the homeserver currently returns M_NOT_FOUND when there is no key backup
|
||||||
callback.onSuccess(null)
|
callback.onSuccess(null)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1209,8 +1209,8 @@ internal class KeysBackup @Inject constructor(
|
||||||
Timber.e(failure, "backupKeys: backupKeys failed.")
|
Timber.e(failure, "backupKeys: backupKeys failed.")
|
||||||
|
|
||||||
when (failure.error.code) {
|
when (failure.error.code) {
|
||||||
MatrixError.NOT_FOUND,
|
MatrixError.M_NOT_FOUND,
|
||||||
MatrixError.WRONG_ROOM_KEYS_VERSION -> {
|
MatrixError.M_WRONG_ROOM_KEYS_VERSION -> {
|
||||||
// Backup has been deleted on the server, or we are not using the last backup version
|
// Backup has been deleted on the server, or we are not using the last backup version
|
||||||
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
|
keysBackupStateManager.state = KeysBackupState.WrongBackUpVersion
|
||||||
backupAllGroupSessionsCallback?.onFailure(failure)
|
backupAllGroupSessionsCallback?.onFailure(failure)
|
||||||
|
|
|
@ -68,7 +68,9 @@ internal class RoomSummaryMapper @Inject constructor(
|
||||||
membership = roomSummaryEntity.membership,
|
membership = roomSummaryEntity.membership,
|
||||||
versioningState = roomSummaryEntity.versioningState,
|
versioningState = roomSummaryEntity.versioningState,
|
||||||
readMarkerId = roomSummaryEntity.readMarkerId,
|
readMarkerId = roomSummaryEntity.readMarkerId,
|
||||||
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList()
|
userDrafts = roomSummaryEntity.userDrafts?.userDrafts?.map { DraftMapper.map(it) } ?: emptyList(),
|
||||||
|
canonicalAlias = roomSummaryEntity.canonicalAlias,
|
||||||
|
aliases = roomSummaryEntity.aliases.toList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,10 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
||||||
var hasUnreadMessages: Boolean = false,
|
var hasUnreadMessages: Boolean = false,
|
||||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
var tags: RealmList<RoomTagEntity> = RealmList(),
|
||||||
var userDrafts: UserDraftsEntity? = null,
|
var userDrafts: UserDraftsEntity? = null,
|
||||||
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS
|
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
|
||||||
|
var canonicalAlias: String? = null,
|
||||||
|
var aliases: RealmList<String> = RealmList(),
|
||||||
|
var flatAliases: String = ""
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
private var membershipStr: String = Membership.NONE.name
|
private var membershipStr: String = Membership.NONE.name
|
||||||
|
|
|
@ -32,6 +32,18 @@ internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = n
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RoomSummaryEntity.Companion.findByAlias(realm: Realm, roomAlias: String): RoomSummaryEntity? {
|
||||||
|
val roomSummary = realm.where<RoomSummaryEntity>()
|
||||||
|
.equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, roomAlias)
|
||||||
|
.findFirst()
|
||||||
|
if (roomSummary != null) {
|
||||||
|
return roomSummary
|
||||||
|
}
|
||||||
|
return realm.where<RoomSummaryEntity>()
|
||||||
|
.contains(RoomSummaryEntityFields.FLAT_ALIASES, "|$roomAlias")
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomSummaryEntity {
|
internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): RoomSummaryEntity {
|
||||||
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
return where(realm, roomId).findFirst() ?: realm.createObject(roomId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,19 +16,29 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.network
|
package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class AccessTokenInterceptor @Inject constructor(private val credentials: Credentials) : Interceptor {
|
internal class AccessTokenInterceptor @Inject constructor(
|
||||||
|
@UserId private val userId: String,
|
||||||
|
private val sessionParamsStore: SessionParamsStore) : Interceptor {
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
var request = chain.request()
|
var request = chain.request()
|
||||||
val newRequestBuilder = request.newBuilder()
|
|
||||||
// Add the access token to all requests if it is set
|
accessToken?.let {
|
||||||
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + credentials.accessToken)
|
val newRequestBuilder = request.newBuilder()
|
||||||
request = newRequestBuilder.build()
|
// Add the access token to all requests if it is set
|
||||||
|
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer $it")
|
||||||
|
request = newRequestBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
return chain.proceed(request)
|
return chain.proceed(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val accessToken
|
||||||
|
get() = sessionParamsStore.get(userId)?.credentials?.accessToken
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ package im.vector.matrix.android.internal.network
|
||||||
|
|
||||||
import com.squareup.moshi.JsonDataException
|
import com.squareup.moshi.JsonDataException
|
||||||
import com.squareup.moshi.JsonEncodingException
|
import com.squareup.moshi.JsonEncodingException
|
||||||
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
@ -32,6 +32,7 @@ import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.net.HttpURLConnection
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
@ -99,7 +100,11 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int): Failure {
|
||||||
if (matrixError != null) {
|
if (matrixError != null) {
|
||||||
if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) {
|
if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) {
|
||||||
// Also send this error to the bus, for a global management
|
// Also send this error to the bus, for a global management
|
||||||
EventBus.getDefault().post(ConsentNotGivenError(matrixError.consentUri))
|
EventBus.getDefault().post(GlobalError.ConsentNotGivenError(matrixError.consentUri))
|
||||||
|
} else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||||
|
&& matrixError.code == MatrixError.M_UNKNOWN_TOKEN) {
|
||||||
|
// Also send this error to the bus, for a global management
|
||||||
|
EventBus.getDefault().post(GlobalError.InvalidToken(matrixError.isSoftLogout))
|
||||||
}
|
}
|
||||||
|
|
||||||
return Failure.ServerError(matrixError, httpCode)
|
return Failure.ServerError(matrixError, httpCode)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.matrix.android.api.pushrules.PushRuleService
|
import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||||
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
import im.vector.matrix.android.api.session.InitialSyncProgressService
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
@ -42,12 +42,16 @@ import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
import im.vector.matrix.android.api.session.sync.FilterService
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import im.vector.matrix.android.api.session.user.UserService
|
import im.vector.matrix.android.api.session.user.UserService
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer
|
import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
import im.vector.matrix.android.internal.session.sync.job.SyncThread
|
||||||
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
|
@ -76,6 +80,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
private val syncTokenStore: SyncTokenStore,
|
private val syncTokenStore: SyncTokenStore,
|
||||||
private val syncTaskSequencer: SyncTaskSequencer,
|
private val syncTaskSequencer: SyncTaskSequencer,
|
||||||
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
private val contentUploadProgressTracker: ContentUploadStateTracker,
|
||||||
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
|
||||||
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
|
||||||
|
@ -98,6 +103,9 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
|
|
||||||
private var syncThread: SyncThread? = null
|
private var syncThread: SyncThread? = null
|
||||||
|
|
||||||
|
override val isOpenable: Boolean
|
||||||
|
get() = sessionParamsStore.get(myUserId)?.isTokenValid ?: false
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
override fun open() {
|
override fun open() {
|
||||||
assertMainThread()
|
assertMainThread()
|
||||||
|
@ -169,8 +177,16 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onConsentNotGivenError(consentNotGivenError: ConsentNotGivenError) {
|
fun onGlobalError(globalError: GlobalError) {
|
||||||
sessionListeners.dispatchConsentNotGiven(consentNotGivenError)
|
if (globalError is GlobalError.InvalidToken
|
||||||
|
&& globalError.softLogout) {
|
||||||
|
// Mark the token has invalid
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
sessionParamsStore.setTokenInvalid(myUserId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionListeners.dispatchGlobalError(globalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentUrlResolver() = contentUrlResolver
|
override fun contentUrlResolver() = contentUrlResolver
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
import im.vector.matrix.android.api.failure.ConsentNotGivenError
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ internal class SessionListeners @Inject constructor() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dispatchConsentNotGiven(consentNotGivenError: ConsentNotGivenError) {
|
fun dispatchGlobalError(globalError: GlobalError) {
|
||||||
synchronized(listeners) {
|
synchronized(listeners) {
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
it.onConsentNotGivenError(consentNotGivenError)
|
it.onGlobalError(globalError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,13 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.VersioningState
|
import im.vector.matrix.android.api.session.room.model.VersioningState
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
|
@ -45,6 +47,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
private val joinRoomTask: JoinRoomTask,
|
private val joinRoomTask: JoinRoomTask,
|
||||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||||
|
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||||
private val roomFactory: RoomFactory,
|
private val roomFactory: RoomFactory,
|
||||||
private val taskExecutor: TaskExecutor) : RoomService {
|
private val taskExecutor: TaskExecutor) : RoomService {
|
||||||
|
|
||||||
|
@ -111,4 +114,12 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<String>>): Cancelable {
|
||||||
|
return roomIdByAliasTask
|
||||||
|
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.room
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.events.model.Content
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
|
||||||
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
|
||||||
|
@ -258,4 +259,12 @@ internal interface RoomAPI {
|
||||||
fun reportContent(@Path("roomId") roomId: String,
|
fun reportContent(@Path("roomId") roomId: String,
|
||||||
@Path("eventId") eventId: String,
|
@Path("eventId") eventId: String,
|
||||||
@Body body: ReportContentBody): Call<Unit>
|
@Body body: ReportContentBody): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the room ID associated to the room alias.
|
||||||
|
*
|
||||||
|
* @param roomAlias the room alias.
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
||||||
|
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import im.vector.matrix.android.api.session.room.RoomDirectoryService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.internal.session.DefaultFileService
|
import im.vector.matrix.android.internal.session.DefaultFileService
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.DefaultGetRoomIdByAliasTask
|
||||||
|
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask
|
import im.vector.matrix.android.internal.session.room.create.DefaultCreateRoomTask
|
||||||
import im.vector.matrix.android.internal.session.room.directory.DefaultGetPublicRoomTask
|
import im.vector.matrix.android.internal.session.room.directory.DefaultGetPublicRoomTask
|
||||||
|
@ -133,4 +135,7 @@ internal abstract class RoomModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindFetchEditHistoryTask(fetchEditHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
abstract fun bindFetchEditHistoryTask(fetchEditHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetRoomIdByAliasTask(getRoomIdByAliasTask: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.toModel
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
import im.vector.matrix.android.api.session.room.model.Membership
|
import im.vector.matrix.android.api.session.room.model.Membership
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomAliasesContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
import im.vector.matrix.android.api.session.room.model.RoomTopicContent
|
||||||
import im.vector.matrix.android.internal.database.mapper.ContentMapper
|
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.EventEntity
|
||||||
|
@ -68,7 +70,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
||||||
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
unreadNotifications: RoomSyncUnreadNotifications? = null,
|
||||||
updateMembers: Boolean = false) {
|
updateMembers: Boolean = false) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
?: realm.createObject(roomId)
|
?: realm.createObject(roomId)
|
||||||
|
|
||||||
if (roomSummary != null) {
|
if (roomSummary != null) {
|
||||||
if (roomSummary.heroes.isNotEmpty()) {
|
if (roomSummary.heroes.isNotEmpty()) {
|
||||||
|
@ -91,15 +93,24 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
|
||||||
|
|
||||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES)
|
||||||
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
|
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()
|
||||||
|
val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev()
|
||||||
|
val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev()
|
||||||
|
|
||||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||||
// avoid this call if we are sure there are unread events
|
// avoid this call if we are sure there are unread events
|
||||||
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
|
|| !isEventRead(monarchy, userId, roomId, latestPreviewableEvent?.eventId)
|
||||||
|
|
||||||
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
|
||||||
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
|
||||||
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
|
roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
|
||||||
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
||||||
|
roomSummaryEntity.canonicalAlias = ContentMapper.map(lastCanonicalAliasEvent?.content).toModel<RoomCanonicalAliasContent>()
|
||||||
|
?.canonicalAlias
|
||||||
|
|
||||||
|
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases ?: emptyList()
|
||||||
|
roomSummaryEntity.aliases.clear()
|
||||||
|
roomSummaryEntity.aliases.addAll(roomAliases)
|
||||||
|
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
||||||
|
|
||||||
if (updateMembers) {
|
if (updateMembers) {
|
||||||
val otherRoomMembers = RoomMembers(realm, roomId)
|
val otherRoomMembers = RoomMembers(realm, roomId)
|
||||||
|
|
|
@ -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.alias
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.util.Optional
|
||||||
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.findByAlias
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import io.realm.Realm
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
|
||||||
|
data class Params(
|
||||||
|
val roomAlias: String,
|
||||||
|
val searchOnServer: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetRoomIdByAliasTask @Inject constructor(private val monarchy: Monarchy,
|
||||||
|
private val roomAPI: RoomAPI) : GetRoomIdByAliasTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional<String> {
|
||||||
|
var roomId = Realm.getInstance(monarchy.realmConfiguration).use {
|
||||||
|
RoomSummaryEntity.findByAlias(it, params.roomAlias)?.roomId
|
||||||
|
}
|
||||||
|
return if (roomId != null) {
|
||||||
|
Optional.from(roomId)
|
||||||
|
} else if (!params.searchOnServer) {
|
||||||
|
Optional.from<String>(null)
|
||||||
|
} else {
|
||||||
|
roomId = executeRequest<RoomAliasDescription> {
|
||||||
|
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
|
||||||
|
}.roomId
|
||||||
|
Optional.from(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.alias
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class RoomAliasDescription(
|
||||||
|
/**
|
||||||
|
* The room ID for this alias.
|
||||||
|
*/
|
||||||
|
@Json(name = "room_id") val roomId: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of servers that are aware of this room ID.
|
||||||
|
*/
|
||||||
|
@Json(name = "servers") val servers: List<String> = emptyList()
|
||||||
|
)
|
|
@ -79,7 +79,7 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam
|
||||||
|
|
||||||
private fun Throwable.shouldBeRetried(): Boolean {
|
private fun Throwable.shouldBeRetried(): Boolean {
|
||||||
return this is Failure.NetworkConnection
|
return this is Failure.NetworkConnection
|
||||||
|| (this is Failure.ServerError && this.error.code == MatrixError.LIMIT_EXCEEDED)
|
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) {
|
private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) {
|
||||||
|
|
|
@ -65,7 +65,7 @@ internal class TextPillsUtils @Inject constructor(
|
||||||
// append text before pill
|
// append text before pill
|
||||||
append(text, currIndex, start)
|
append(text, currIndex, start)
|
||||||
// append the pill
|
// append the pill
|
||||||
append(String.format(template, urlSpan.userId, urlSpan.displayName))
|
append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.displayName))
|
||||||
currIndex = end
|
currIndex = end
|
||||||
}
|
}
|
||||||
// append text after the last pill
|
// append text after the last pill
|
||||||
|
|
|
@ -17,17 +17,43 @@
|
||||||
package im.vector.matrix.android.internal.session.signout
|
package im.vector.matrix.android.internal.session.signout
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
|
||||||
|
private val signInAgainTask: SignInAgainTask,
|
||||||
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor) : SignOutService {
|
private val taskExecutor: TaskExecutor) : SignOutService {
|
||||||
|
|
||||||
override fun signOut(callback: MatrixCallback<Unit>) {
|
override fun signInAgain(password: String,
|
||||||
signOutTask
|
callback: MatrixCallback<Unit>): Cancelable {
|
||||||
.configureWith {
|
return signInAgainTask
|
||||||
|
.configureWith(SignInAgainTask.Params(password)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateCredentials(credentials: Credentials,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return GlobalScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
sessionParamsStore.updateCredentials(credentials)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun signOut(sigOutFromHomeserver: Boolean,
|
||||||
|
callback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return signOutTask
|
||||||
|
.configureWith(SignOutTask.Params(sigOutFromHomeserver)) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
|
|
|
@ -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.internal.session.signout
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SignInAgainTask : Task<SignInAgainTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val password: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSignInAgainTask @Inject constructor(
|
||||||
|
private val signOutAPI: SignOutAPI,
|
||||||
|
private val sessionParams: SessionParams,
|
||||||
|
private val sessionParamsStore: SessionParamsStore) : SignInAgainTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: SignInAgainTask.Params) {
|
||||||
|
val newCredentials = executeRequest<Credentials> {
|
||||||
|
apiCall = signOutAPI.loginAgain(
|
||||||
|
PasswordLoginParams.userIdentifier(
|
||||||
|
// Reuse the same userId
|
||||||
|
sessionParams.credentials.userId,
|
||||||
|
params.password,
|
||||||
|
// The spec says the initial device name will be ignored
|
||||||
|
// https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
|
||||||
|
// but https://github.com/matrix-org/synapse/issues/6525
|
||||||
|
// Reuse the same deviceId
|
||||||
|
deviceId = sessionParams.credentials.deviceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionParamsStore.updateCredentials(newCredentials)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,12 +16,27 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.signout
|
package im.vector.matrix.android.internal.session.signout
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.Headers
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
|
||||||
internal interface SignOutAPI {
|
internal interface SignOutAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to login again to the same account.
|
||||||
|
* Set all the timeouts to 1 minute
|
||||||
|
* It is similar to [AuthAPI.login]
|
||||||
|
*
|
||||||
|
* @param loginParams the login parameters
|
||||||
|
*/
|
||||||
|
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||||
|
fun loginAgain(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate the access token, so that it can no longer be used for authorization.
|
* Invalidate the access token, so that it can no longer be used for authorization.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -37,8 +37,11 @@ internal abstract class SignOutModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSignOutTask(signOutTask: DefaultSignOutTask): SignOutTask
|
abstract fun bindSignOutTask(task: DefaultSignOutTask): SignOutTask
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSignOutService(signOutService: DefaultSignOutService): SignOutService
|
abstract fun bindSignInAgainTask(task: DefaultSignInAgainTask): SignInAgainTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSignOutService(service: DefaultSignOutService): SignOutService
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.session.signout
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.internal.SessionManager
|
import im.vector.matrix.android.internal.SessionManager
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.crypto.CryptoModule
|
import im.vector.matrix.android.internal.crypto.CryptoModule
|
||||||
|
@ -32,9 +34,14 @@ import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.HttpURLConnection
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface SignOutTask : Task<Unit, Unit>
|
internal interface SignOutTask : Task<SignOutTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val sigOutFromHomeserver: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal class DefaultSignOutTask @Inject constructor(private val context: Context,
|
internal class DefaultSignOutTask @Inject constructor(private val context: Context,
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
|
@ -49,10 +56,26 @@ internal class DefaultSignOutTask @Inject constructor(private val context: Conte
|
||||||
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
|
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
|
||||||
@UserMd5 private val userMd5: String) : SignOutTask {
|
@UserMd5 private val userMd5: String) : SignOutTask {
|
||||||
|
|
||||||
override suspend fun execute(params: Unit) {
|
override suspend fun execute(params: SignOutTask.Params) {
|
||||||
Timber.d("SignOut: send request...")
|
// It should be done even after a soft logout, to be sure the deviceId is deleted on the
|
||||||
executeRequest<Unit> {
|
if (params.sigOutFromHomeserver) {
|
||||||
apiCall = signOutAPI.signOut()
|
Timber.d("SignOut: send request...")
|
||||||
|
try {
|
||||||
|
executeRequest<Unit> {
|
||||||
|
apiCall = signOutAPI.signOut()
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
// Maybe due to https://github.com/matrix-org/synapse/issues/5755
|
||||||
|
if (throwable is Failure.ServerError
|
||||||
|
&& throwable.httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||||
|
&& throwable.error.code == MatrixError.M_UNKNOWN_TOKEN) {
|
||||||
|
// Also throwable.error.isSoftLogout should be true
|
||||||
|
// Ignore
|
||||||
|
Timber.w("Ignore error due to https://github.com/matrix-org/synapse/issues/5755")
|
||||||
|
} else {
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.d("SignOut: release session...")
|
Timber.d("SignOut: release session...")
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
package im.vector.matrix.android.internal.session.sync
|
package im.vector.matrix.android.internal.session.sync
|
||||||
|
|
||||||
import im.vector.matrix.android.R
|
import im.vector.matrix.android.R
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
|
||||||
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
import im.vector.matrix.android.internal.auth.SessionParamsStore
|
||||||
import im.vector.matrix.android.internal.di.UserId
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
@ -74,17 +72,8 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
|
||||||
initialSyncProgressService.endAll()
|
initialSyncProgressService.endAll()
|
||||||
initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100)
|
initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100)
|
||||||
}
|
}
|
||||||
val syncResponse = try {
|
val syncResponse = executeRequest<SyncResponse> {
|
||||||
executeRequest<SyncResponse> {
|
apiCall = syncAPI.sync(requestParams)
|
||||||
apiCall = syncAPI.sync(requestParams)
|
|
||||||
}
|
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
// Intercept 401
|
|
||||||
if (throwable is Failure.ServerError
|
|
||||||
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
|
|
||||||
sessionParamsStore.delete(userId)
|
|
||||||
}
|
|
||||||
throw throwable
|
|
||||||
}
|
}
|
||||||
syncResponseHandler.handleResponse(syncResponse, token)
|
syncResponseHandler.handleResponse(syncResponse, token)
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.api.failure.isTokenError
|
import im.vector.matrix.android.api.failure.isTokenError
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTask
|
import im.vector.matrix.android.internal.session.sync.SyncTask
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData
|
||||||
import com.squareup.moshi.JsonEncodingException
|
import com.squareup.moshi.JsonEncodingException
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
|
import im.vector.matrix.android.api.failure.isTokenError
|
||||||
import im.vector.matrix.android.api.session.sync.SyncState
|
import im.vector.matrix.android.api.session.sync.SyncState
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
import im.vector.matrix.android.internal.session.sync.SyncTask
|
import im.vector.matrix.android.internal.session.sync.SyncTask
|
||||||
|
@ -38,19 +39,20 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
private val backgroundDetectionObserver: BackgroundDetectionObserver)
|
private val backgroundDetectionObserver: BackgroundDetectionObserver)
|
||||||
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
|
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
|
||||||
|
|
||||||
private var state: SyncState = SyncState.IDLE
|
private var state: SyncState = SyncState.Idle
|
||||||
private var liveState = MutableLiveData<SyncState>()
|
private var liveState = MutableLiveData<SyncState>()
|
||||||
private val lock = Object()
|
private val lock = Object()
|
||||||
private val syncScope = CoroutineScope(SupervisorJob())
|
private val syncScope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
private var isStarted = false
|
private var isStarted = false
|
||||||
|
private var isTokenValid = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
updateStateTo(SyncState.IDLE)
|
updateStateTo(SyncState.Idle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setInitialForeground(initialForeground: Boolean) {
|
fun setInitialForeground(initialForeground: Boolean) {
|
||||||
val newState = if (initialForeground) SyncState.IDLE else SyncState.PAUSED
|
val newState = if (initialForeground) SyncState.Idle else SyncState.Paused
|
||||||
updateStateTo(newState)
|
updateStateTo(newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +60,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
if (!isStarted) {
|
if (!isStarted) {
|
||||||
Timber.v("Resume sync...")
|
Timber.v("Resume sync...")
|
||||||
isStarted = true
|
isStarted = true
|
||||||
|
// Check again the token validity
|
||||||
|
isTokenValid = true
|
||||||
lock.notify()
|
lock.notify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +76,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
|
|
||||||
fun kill() = synchronized(lock) {
|
fun kill() = synchronized(lock) {
|
||||||
Timber.v("Kill sync...")
|
Timber.v("Kill sync...")
|
||||||
updateStateTo(SyncState.KILLING)
|
updateStateTo(SyncState.Killing)
|
||||||
syncScope.coroutineContext.cancelChildren()
|
syncScope.coroutineContext.cancelChildren()
|
||||||
lock.notify()
|
lock.notify()
|
||||||
}
|
}
|
||||||
|
@ -93,24 +97,29 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
isStarted = true
|
isStarted = true
|
||||||
networkConnectivityChecker.register(this)
|
networkConnectivityChecker.register(this)
|
||||||
backgroundDetectionObserver.register(this)
|
backgroundDetectionObserver.register(this)
|
||||||
while (state != SyncState.KILLING) {
|
while (state != SyncState.Killing) {
|
||||||
Timber.v("Entering loop, state: $state")
|
Timber.v("Entering loop, state: $state")
|
||||||
if (!networkConnectivityChecker.hasInternetAccess()) {
|
if (!networkConnectivityChecker.hasInternetAccess()) {
|
||||||
Timber.v("No network. Waiting...")
|
Timber.v("No network. Waiting...")
|
||||||
updateStateTo(SyncState.NO_NETWORK)
|
updateStateTo(SyncState.NoNetwork)
|
||||||
synchronized(lock) { lock.wait() }
|
synchronized(lock) { lock.wait() }
|
||||||
Timber.v("...unlocked")
|
Timber.v("...unlocked")
|
||||||
} else if (!isStarted) {
|
} else if (!isStarted) {
|
||||||
Timber.v("Sync is Paused. Waiting...")
|
Timber.v("Sync is Paused. Waiting...")
|
||||||
updateStateTo(SyncState.PAUSED)
|
updateStateTo(SyncState.Paused)
|
||||||
|
synchronized(lock) { lock.wait() }
|
||||||
|
Timber.v("...unlocked")
|
||||||
|
} else if (!isTokenValid) {
|
||||||
|
Timber.v("Token is invalid. Waiting...")
|
||||||
|
updateStateTo(SyncState.InvalidToken)
|
||||||
synchronized(lock) { lock.wait() }
|
synchronized(lock) { lock.wait() }
|
||||||
Timber.v("...unlocked")
|
Timber.v("...unlocked")
|
||||||
} else {
|
} else {
|
||||||
if (state !is SyncState.RUNNING) {
|
if (state !is SyncState.Running) {
|
||||||
updateStateTo(SyncState.RUNNING(afterPause = true))
|
updateStateTo(SyncState.Running(afterPause = true))
|
||||||
}
|
}
|
||||||
// No timeout after a pause
|
// No timeout after a pause
|
||||||
val timeout = state.let { if (it is SyncState.RUNNING && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
||||||
Timber.v("Execute sync request with timeout $timeout")
|
Timber.v("Execute sync request with timeout $timeout")
|
||||||
val params = SyncTask.Params(timeout)
|
val params = SyncTask.Params(timeout)
|
||||||
val sync = syncScope.launch {
|
val sync = syncScope.launch {
|
||||||
|
@ -123,7 +132,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("Sync killed")
|
Timber.v("Sync killed")
|
||||||
updateStateTo(SyncState.KILLED)
|
updateStateTo(SyncState.Killed)
|
||||||
backgroundDetectionObserver.unregister(this)
|
backgroundDetectionObserver.unregister(this)
|
||||||
networkConnectivityChecker.unregister(this)
|
networkConnectivityChecker.unregister(this)
|
||||||
}
|
}
|
||||||
|
@ -137,11 +146,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
Timber.v("Timeout")
|
Timber.v("Timeout")
|
||||||
} else if (failure is Failure.Cancelled) {
|
} else if (failure is Failure.Cancelled) {
|
||||||
Timber.v("Cancelled")
|
Timber.v("Cancelled")
|
||||||
} else if (failure is Failure.ServerError
|
} else if (failure.isTokenError()) {
|
||||||
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
|
|
||||||
// No token or invalid token, stop the thread
|
// No token or invalid token, stop the thread
|
||||||
Timber.w(failure)
|
Timber.w(failure)
|
||||||
updateStateTo(SyncState.KILLING)
|
isStarted = false
|
||||||
|
isTokenValid = false
|
||||||
} else {
|
} else {
|
||||||
Timber.e(failure)
|
Timber.e(failure)
|
||||||
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
|
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
|
||||||
|
@ -152,8 +161,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
state.let {
|
state.let {
|
||||||
if (it is SyncState.RUNNING && it.afterPause) {
|
if (it is SyncState.Running && it.afterPause) {
|
||||||
updateStateTo(SyncState.RUNNING(afterPause = false))
|
updateStateTo(SyncState.Running(afterPause = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.util
|
package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a string to an UTF8 String
|
* Convert a string to an UTF8 String
|
||||||
|
@ -51,10 +49,3 @@ fun convertFromUTF8(s: String): String {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String?.firstLetterOfDisplayName(): String {
|
|
||||||
if (this.isNullOrEmpty()) return ""
|
|
||||||
val isUserId = MatrixPatterns.isUserId(this)
|
|
||||||
val firstLetterIndex = if (isUserId) 1 else 0
|
|
||||||
return this[firstLetterIndex].toString().toUpperCase(Locale.ROOT)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx'
|
include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch'
|
||||||
|
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 10
|
ext.versionMinor = 11
|
||||||
ext.versionPatch = 0
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
|
@ -229,6 +229,7 @@ dependencies {
|
||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
|
implementation project(":diff-match-patch")
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
@ -341,8 +342,6 @@ dependencies {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'diff_match_patch:diff_match_patch:current'
|
|
||||||
|
|
||||||
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
implementation "androidx.emoji:emoji-appcompat:1.0.0"
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
|
|
|
@ -65,7 +65,13 @@
|
||||||
<activity android:name=".features.roomdirectory.RoomDirectoryActivity" />
|
<activity android:name=".features.roomdirectory.RoomDirectoryActivity" />
|
||||||
<activity android:name=".features.roomdirectory.roompreview.RoomPreviewActivity" />
|
<activity android:name=".features.roomdirectory.roompreview.RoomPreviewActivity" />
|
||||||
<activity android:name=".features.home.room.filtered.FilteredRoomsActivity" />
|
<activity android:name=".features.home.room.filtered.FilteredRoomsActivity" />
|
||||||
<activity android:name=".features.home.room.detail.RoomDetailActivity" />
|
<activity
|
||||||
|
android:name=".features.home.room.detail.RoomDetailActivity"
|
||||||
|
android:parentActivityName=".features.home.HomeActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value=".features.home.HomeActivity" />
|
||||||
|
</activity>
|
||||||
<activity android:name=".features.debug.DebugMenuActivity" />
|
<activity android:name=".features.debug.DebugMenuActivity" />
|
||||||
<activity android:name=".features.home.createdirect.CreateDirectRoomActivity" />
|
<activity android:name=".features.home.createdirect.CreateDirectRoomActivity" />
|
||||||
<activity android:name=".features.webview.VectorWebViewActivity" />
|
<activity android:name=".features.webview.VectorWebViewActivity" />
|
||||||
|
@ -98,6 +104,22 @@
|
||||||
<category android:name="android.intent.category.OPENABLE" />
|
<category android:name="android.intent.category.OPENABLE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".features.signout.hard.SignedOutActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".features.signout.soft.SoftLogoutActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
<activity android:name=".features.permalink.PermalinkHandlerActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="matrix.to" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -37,7 +37,7 @@ class ActiveSessionHolder @Inject constructor(private val authenticationService:
|
||||||
|
|
||||||
fun setActiveSession(session: Session) {
|
fun setActiveSession(session: Session) {
|
||||||
activeSession.set(session)
|
activeSession.set(session)
|
||||||
sessionObservableStore.post(Option.fromNullable(session))
|
sessionObservableStore.post(Option.just(session))
|
||||||
keyRequestHandler.start(session)
|
keyRequestHandler.start(session)
|
||||||
incomingVerificationRequestHandler.start(session)
|
incomingVerificationRequestHandler.start(session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewNoPreviewFr
|
||||||
import im.vector.riotx.features.settings.*
|
import im.vector.riotx.features.settings.*
|
||||||
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
|
||||||
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
import im.vector.riotx.features.settings.push.PushGatewaysFragment
|
||||||
|
import im.vector.riotx.features.signout.soft.SoftLogoutFragment
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
interface FragmentModule {
|
interface FragmentModule {
|
||||||
|
@ -261,4 +262,9 @@ interface FragmentModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FragmentKey(EmojiChooserFragment::class)
|
@FragmentKey(EmojiChooserFragment::class)
|
||||||
fun bindEmojiChooserFragment(fragment: EmojiChooserFragment): Fragment
|
fun bindEmojiChooserFragment(fragment: EmojiChooserFragment): Fragment
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FragmentKey(SoftLogoutFragment::class)
|
||||||
|
fun bindSoftLogoutFragment(fragment: SoftLogoutFragment): Fragment
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.fragment.app.FragmentFactory
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import dagger.BindsInstance
|
import dagger.BindsInstance
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.preference.UserAvatarPreference
|
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||||
import im.vector.riotx.features.MainActivity
|
import im.vector.riotx.features.MainActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
|
@ -40,6 +41,7 @@ import im.vector.riotx.features.login.LoginActivity
|
||||||
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
import im.vector.riotx.features.media.ImageMediaViewerActivity
|
||||||
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
import im.vector.riotx.features.media.VideoMediaViewerActivity
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
|
import im.vector.riotx.features.permalink.PermalinkHandlerActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReportActivity
|
import im.vector.riotx.features.rageshake.BugReportActivity
|
||||||
import im.vector.riotx.features.rageshake.BugReporter
|
import im.vector.riotx.features.rageshake.BugReporter
|
||||||
import im.vector.riotx.features.rageshake.RageShake
|
import im.vector.riotx.features.rageshake.RageShake
|
||||||
|
@ -49,6 +51,7 @@ import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity
|
||||||
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity
|
||||||
import im.vector.riotx.features.settings.VectorSettingsActivity
|
import im.vector.riotx.features.settings.VectorSettingsActivity
|
||||||
import im.vector.riotx.features.share.IncomingShareActivity
|
import im.vector.riotx.features.share.IncomingShareActivity
|
||||||
|
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
||||||
import im.vector.riotx.features.ui.UiStateRepository
|
import im.vector.riotx.features.ui.UiStateRepository
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
|
@ -78,6 +81,8 @@ interface ScreenComponent {
|
||||||
|
|
||||||
fun navigator(): Navigator
|
fun navigator(): Navigator
|
||||||
|
|
||||||
|
fun errorFormatter(): ErrorFormatter
|
||||||
|
|
||||||
fun uiStateRepository(): UiStateRepository
|
fun uiStateRepository(): UiStateRepository
|
||||||
|
|
||||||
fun inject(activity: HomeActivity)
|
fun inject(activity: HomeActivity)
|
||||||
|
@ -126,6 +131,10 @@ interface ScreenComponent {
|
||||||
|
|
||||||
fun inject(roomListActionsBottomSheet: RoomListQuickActionsBottomSheet)
|
fun inject(roomListActionsBottomSheet: RoomListQuickActionsBottomSheet)
|
||||||
|
|
||||||
|
fun inject(activity: SoftLogoutActivity)
|
||||||
|
|
||||||
|
fun inject(permalinkHandlerActivity: PermalinkHandlerActivity)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(vectorComponent: VectorComponent,
|
fun create(vectorComponent: VectorComponent,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.riotx.ActiveSessionDataSource
|
||||||
import im.vector.riotx.EmojiCompatFontProvider
|
import im.vector.riotx.EmojiCompatFontProvider
|
||||||
import im.vector.riotx.EmojiCompatWrapper
|
import im.vector.riotx.EmojiCompatWrapper
|
||||||
import im.vector.riotx.VectorApplication
|
import im.vector.riotx.VectorApplication
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.pushers.PushersManager
|
import im.vector.riotx.core.pushers.PushersManager
|
||||||
import im.vector.riotx.core.utils.AssetReader
|
import im.vector.riotx.core.utils.AssetReader
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
@ -88,6 +89,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun navigator(): Navigator
|
fun navigator(): Navigator
|
||||||
|
|
||||||
|
fun errorFormatter(): ErrorFormatter
|
||||||
|
|
||||||
fun homeRoomListObservableStore(): HomeRoomListDataSource
|
fun homeRoomListObservableStore(): HomeRoomListDataSource
|
||||||
|
|
||||||
fun shareRoomListObservableStore(): ShareRoomListDataSource
|
fun shareRoomListObservableStore(): ShareRoomListDataSource
|
||||||
|
|
|
@ -26,6 +26,8 @@ import dagger.Provides
|
||||||
import im.vector.matrix.android.api.Matrix
|
import im.vector.matrix.android.api.Matrix
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.riotx.core.error.DefaultErrorFormatter
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.features.navigation.DefaultNavigator
|
import im.vector.riotx.features.navigation.DefaultNavigator
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import im.vector.riotx.features.ui.SharedPreferencesUiStateRepository
|
import im.vector.riotx.features.ui.SharedPreferencesUiStateRepository
|
||||||
|
@ -72,6 +74,9 @@ abstract class VectorModule {
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindNavigator(navigator: DefaultNavigator): Navigator
|
abstract fun bindNavigator(navigator: DefaultNavigator): Navigator
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindErrorFormatter(errorFormatter: DefaultErrorFormatter): ErrorFormatter
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUiStateRepository(uiStateRepository: SharedPreferencesUiStateRepository): UiStateRepository
|
abstract fun bindUiStateRepository(uiStateRepository: SharedPreferencesUiStateRepository): UiStateRepository
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
@ -37,11 +38,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var avatarRenderer: AvatarRenderer
|
lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var avatarUrl: String
|
lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute
|
|
||||||
lateinit var senderId: String
|
|
||||||
@EpoxyAttribute
|
|
||||||
var senderName: String? = null
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var body: CharSequence
|
lateinit var body: CharSequence
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
|
@ -50,8 +47,8 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessa
|
||||||
var movementMethod: MovementMethod? = null
|
var movementMethod: MovementMethod? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
avatarRenderer.render(avatarUrl, senderId, senderName, holder.avatar)
|
avatarRenderer.render(matrixItem, holder.avatar)
|
||||||
holder.sender.setTextOrHide(senderName)
|
holder.sender.setTextOrHide(matrixItem.displayName)
|
||||||
holder.body.movementMethod = movementMethod
|
holder.body.movementMethod = movementMethod
|
||||||
holder.body.text = body
|
holder.body.text = body
|
||||||
body.findPillsAndProcess { it.bind(holder.body) }
|
body.findPillsAndProcess { it.bind(holder.body) }
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
@ -36,16 +37,12 @@ abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel<BottomSheetRoomPrev
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var avatarRenderer: AvatarRenderer
|
lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var avatarUrl: String
|
lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute
|
|
||||||
lateinit var roomId: String
|
|
||||||
@EpoxyAttribute
|
|
||||||
var roomName: String? = null
|
|
||||||
@EpoxyAttribute var settingsClickListener: View.OnClickListener? = null
|
@EpoxyAttribute var settingsClickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
avatarRenderer.render(avatarUrl, roomId, roomName, holder.avatar)
|
avatarRenderer.render(matrixItem, holder.avatar)
|
||||||
holder.roomName.setTextOrHide(roomName)
|
holder.roomName.setTextOrHide(matrixItem.displayName)
|
||||||
holder.roomSettings.setOnClickListener(settingsClickListener)
|
holder.roomSettings.setOnClickListener(settingsClickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,15 @@ import java.net.SocketTimeoutException
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ErrorFormatter @Inject constructor(private val stringProvider: StringProvider) {
|
interface ErrorFormatter {
|
||||||
|
fun toHumanReadable(throwable: Throwable?): String
|
||||||
|
}
|
||||||
|
|
||||||
fun toHumanReadable(failure: Failure): String {
|
class DefaultErrorFormatter @Inject constructor(
|
||||||
// Default
|
private val stringProvider: StringProvider
|
||||||
return failure.localizedMessage
|
) : ErrorFormatter {
|
||||||
}
|
|
||||||
|
|
||||||
fun toHumanReadable(throwable: Throwable?): String {
|
override fun toHumanReadable(throwable: Throwable?): String {
|
||||||
return when (throwable) {
|
return when (throwable) {
|
||||||
null -> null
|
null -> null
|
||||||
is Failure.NetworkConnection -> {
|
is Failure.NetworkConnection -> {
|
||||||
|
@ -41,6 +42,7 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
|
||||||
stringProvider.getString(R.string.error_network_timeout)
|
stringProvider.getString(R.string.error_network_timeout)
|
||||||
throwable.ioException is UnknownHostException ->
|
throwable.ioException is UnknownHostException ->
|
||||||
// Invalid homeserver?
|
// Invalid homeserver?
|
||||||
|
// TODO Check network state, airplane mode, etc.
|
||||||
stringProvider.getString(R.string.login_error_unknown_host)
|
stringProvider.getString(R.string.login_error_unknown_host)
|
||||||
else ->
|
else ->
|
||||||
stringProvider.getString(R.string.error_no_network)
|
stringProvider.getString(R.string.error_no_network)
|
||||||
|
@ -52,23 +54,23 @@ class ErrorFormatter @Inject constructor(private val stringProvider: StringProvi
|
||||||
// Special case for terms and conditions
|
// Special case for terms and conditions
|
||||||
stringProvider.getString(R.string.error_terms_not_accepted)
|
stringProvider.getString(R.string.error_terms_not_accepted)
|
||||||
}
|
}
|
||||||
throwable.error.code == MatrixError.FORBIDDEN
|
throwable.error.code == MatrixError.M_FORBIDDEN
|
||||||
&& throwable.error.message == "Invalid password" -> {
|
&& throwable.error.message == "Invalid password" -> {
|
||||||
stringProvider.getString(R.string.auth_invalid_login_param)
|
stringProvider.getString(R.string.auth_invalid_login_param)
|
||||||
}
|
}
|
||||||
throwable.error.code == MatrixError.USER_IN_USE -> {
|
throwable.error.code == MatrixError.M_USER_IN_USE -> {
|
||||||
stringProvider.getString(R.string.login_signup_error_user_in_use)
|
stringProvider.getString(R.string.login_signup_error_user_in_use)
|
||||||
}
|
}
|
||||||
throwable.error.code == MatrixError.BAD_JSON -> {
|
throwable.error.code == MatrixError.M_BAD_JSON -> {
|
||||||
stringProvider.getString(R.string.login_error_bad_json)
|
stringProvider.getString(R.string.login_error_bad_json)
|
||||||
}
|
}
|
||||||
throwable.error.code == MatrixError.NOT_JSON -> {
|
throwable.error.code == MatrixError.M_NOT_JSON -> {
|
||||||
stringProvider.getString(R.string.login_error_not_json)
|
stringProvider.getString(R.string.login_error_not_json)
|
||||||
}
|
}
|
||||||
throwable.error.code == MatrixError.LIMIT_EXCEEDED -> {
|
throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {
|
||||||
limitExceededError(throwable.error)
|
limitExceededError(throwable.error)
|
||||||
}
|
}
|
||||||
throwable.error.code == MatrixError.THREEPID_NOT_FOUND -> {
|
throwable.error.code == MatrixError.M_THREEPID_NOT_FOUND -> {
|
||||||
stringProvider.getString(R.string.login_reset_password_error_not_found)
|
stringProvider.getString(R.string.login_reset_password_error_not_found)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
@ -21,6 +21,6 @@ import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
fun Throwable.is401(): Boolean {
|
fun Throwable.is401(): Boolean {
|
||||||
return (this is Failure.ServerError && this.httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
return (this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */
|
||||||
&& this.error.code == MatrixError.UNAUTHORIZED)
|
&& error.code == MatrixError.M_UNAUTHORIZED)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.sync.FilterService
|
import im.vector.matrix.android.api.session.sync.FilterService
|
||||||
import im.vector.riotx.core.services.VectorSyncService
|
import im.vector.riotx.core.services.VectorSyncService
|
||||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
|
@ -59,3 +60,11 @@ fun Session.startSyncing(context: Context) {
|
||||||
Timber.v("--> is at least started? $isAtLeastStarted")
|
Timber.v("--> is at least started? $isAtLeastStarted")
|
||||||
startSync(isAtLeastStarted)
|
startSync(isAtLeastStarted)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tell is the session has unsaved e2e keys in the backup
|
||||||
|
*/
|
||||||
|
fun Session.hasUnsavedKeys(): Boolean {
|
||||||
|
return inboundGroupSessionsCount(false) > 0
|
||||||
|
&& getKeysBackupService().state != KeysBackupState.ReadyToBackUp
|
||||||
|
}
|
||||||
|
|
|
@ -35,3 +35,12 @@ fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ex: "https://matrix.org/" -> "matrix.org"
|
||||||
|
*/
|
||||||
|
fun String?.toReducedUrl(): String {
|
||||||
|
return (this ?: "")
|
||||||
|
.substringAfter("://")
|
||||||
|
.trim { it == '/' }
|
||||||
|
}
|
||||||
|
|
|
@ -38,12 +38,15 @@ import butterknife.Unbinder
|
||||||
import com.airbnb.mvrx.MvRx
|
import com.airbnb.mvrx.MvRx
|
||||||
import com.bumptech.glide.util.Util
|
import com.bumptech.glide.util.Util
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.riotx.BuildConfig
|
import im.vector.riotx.BuildConfig
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.*
|
import im.vector.riotx.core.di.*
|
||||||
import im.vector.riotx.core.dialogs.DialogLocker
|
import im.vector.riotx.core.dialogs.DialogLocker
|
||||||
import im.vector.riotx.core.extensions.observeEvent
|
import im.vector.riotx.core.extensions.observeEvent
|
||||||
import im.vector.riotx.core.utils.toast
|
import im.vector.riotx.core.utils.toast
|
||||||
|
import im.vector.riotx.features.MainActivity
|
||||||
|
import im.vector.riotx.features.MainActivityArgs
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
import im.vector.riotx.features.consent.ConsentNotGivenHelper
|
import im.vector.riotx.features.consent.ConsentNotGivenHelper
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
|
@ -89,6 +92,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
protected lateinit var navigator: Navigator
|
protected lateinit var navigator: Navigator
|
||||||
private lateinit var activeSessionHolder: ActiveSessionHolder
|
private lateinit var activeSessionHolder: ActiveSessionHolder
|
||||||
|
|
||||||
|
// Filter for multiple invalid token error
|
||||||
|
private var mainActivityStarted = false
|
||||||
|
|
||||||
private var unBinder: Unbinder? = null
|
private var unBinder: Unbinder? = null
|
||||||
|
|
||||||
private var savedInstanceState: Bundle? = null
|
private var savedInstanceState: Bundle? = null
|
||||||
|
@ -153,9 +159,8 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
})
|
})
|
||||||
|
|
||||||
sessionListener = getVectorComponent().sessionListener()
|
sessionListener = getVectorComponent().sessionListener()
|
||||||
sessionListener.consentNotGivenLiveData.observeEvent(this) {
|
sessionListener.globalErrorLiveData.observeEvent(this) {
|
||||||
consentNotGivenHelper.displayDialog(it.consentUri,
|
handleGlobalError(it)
|
||||||
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doBeforeSetContentView()
|
doBeforeSetContentView()
|
||||||
|
@ -180,6 +185,33 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleGlobalError(globalError: GlobalError) {
|
||||||
|
when (globalError) {
|
||||||
|
is GlobalError.InvalidToken ->
|
||||||
|
handleInvalidToken(globalError)
|
||||||
|
is GlobalError.ConsentNotGivenError ->
|
||||||
|
consentNotGivenHelper.displayDialog(globalError.consentUri,
|
||||||
|
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
|
||||||
|
Timber.w("Invalid token event received")
|
||||||
|
if (mainActivityStarted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mainActivityStarted = true
|
||||||
|
|
||||||
|
MainActivity.restartApp(this,
|
||||||
|
MainActivityArgs(
|
||||||
|
clearCredentials = !globalError.softLogout,
|
||||||
|
isUserLoggedOut = true,
|
||||||
|
isSoftLogout = globalError.softLogout
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
unBinder?.unbind()
|
unBinder?.unbind()
|
||||||
|
|
|
@ -34,6 +34,7 @@ import com.bumptech.glide.util.Util.assertMainThread
|
||||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||||
import im.vector.riotx.core.di.HasScreenInjector
|
import im.vector.riotx.core.di.HasScreenInjector
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
import im.vector.riotx.features.navigation.Navigator
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
@ -49,12 +50,14 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Navigator
|
* Navigator and other common objects
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
protected lateinit var navigator: Navigator
|
|
||||||
private lateinit var screenComponent: ScreenComponent
|
private lateinit var screenComponent: ScreenComponent
|
||||||
|
|
||||||
|
protected lateinit var navigator: Navigator
|
||||||
|
protected lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* View model
|
* View model
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
@ -74,6 +77,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
|
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
|
||||||
navigator = screenComponent.navigator()
|
navigator = screenComponent.navigator()
|
||||||
|
errorFormatter = screenComponent.errorFormatter()
|
||||||
viewModelFactory = screenComponent.viewModelFactory()
|
viewModelFactory = screenComponent.viewModelFactory()
|
||||||
childFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
childFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
||||||
injectWith(injector())
|
injectWith(injector())
|
||||||
|
|
|
@ -23,6 +23,8 @@ import android.widget.ProgressBar
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceViewHolder
|
import androidx.preference.PreferenceViewHolder
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.vectorComponent
|
import im.vector.riotx.core.extensions.vectorComponent
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
@ -59,9 +61,9 @@ open class UserAvatarPreference : Preference {
|
||||||
val session = mSession ?: return
|
val session = mSession ?: return
|
||||||
val view = mAvatarView ?: return
|
val view = mAvatarView ?: return
|
||||||
session.getUser(session.myUserId)?.let {
|
session.getUser(session.myUserId)?.let {
|
||||||
avatarRenderer.render(it, view)
|
avatarRenderer.render(it.toMatrixItem(), view)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
avatarRenderer.render(null, session.myUserId, null, view)
|
avatarRenderer.render(MatrixItem.UserItem(session.myUserId), view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.toMatrixItem
|
||||||
import kotlinx.android.synthetic.main.view_read_receipts.view.*
|
import kotlinx.android.synthetic.main.view_read_receipts.view.*
|
||||||
|
|
||||||
private const val MAX_RECEIPT_DISPLAYED = 5
|
private const val MAX_RECEIPT_DISPLAYED = 5
|
||||||
|
@ -59,7 +60,7 @@ class ReadReceiptsView @JvmOverloads constructor(
|
||||||
receiptAvatars[index].visibility = View.INVISIBLE
|
receiptAvatars[index].visibility = View.INVISIBLE
|
||||||
} else {
|
} else {
|
||||||
receiptAvatars[index].visibility = View.VISIBLE
|
receiptAvatars[index].visibility = View.VISIBLE
|
||||||
avatarRenderer.render(receiptData.avatarUrl, receiptData.userId, receiptData.displayName, receiptAvatars[index])
|
avatarRenderer.render(receiptData.toMatrixItem(), receiptAvatars[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,11 @@ package im.vector.riotx.features
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.failure.GlobalError
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
|
@ -31,6 +33,10 @@ import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
import im.vector.riotx.core.utils.deleteAllFiles
|
import im.vector.riotx.core.utils.deleteAllFiles
|
||||||
import im.vector.riotx.features.home.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
import im.vector.riotx.features.login.LoginActivity
|
import im.vector.riotx.features.login.LoginActivity
|
||||||
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
|
import im.vector.riotx.features.signout.hard.SignedOutActivity
|
||||||
|
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -38,23 +44,37 @@ import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class MainActivityArgs(
|
||||||
|
val clearCache: Boolean = false,
|
||||||
|
val clearCredentials: Boolean = false,
|
||||||
|
val isUserLoggedOut: Boolean = false,
|
||||||
|
val isSoftLogout: Boolean = false
|
||||||
|
) : Parcelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the entry point of RiotX
|
||||||
|
* This Activity, when started with argument, is also doing some cleanup when user disconnects,
|
||||||
|
* clears cache, is logged out, or is soft logged out
|
||||||
|
*/
|
||||||
class MainActivity : VectorBaseActivity() {
|
class MainActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EXTRA_CLEAR_CACHE = "EXTRA_CLEAR_CACHE"
|
private const val EXTRA_ARGS = "EXTRA_ARGS"
|
||||||
private const val EXTRA_CLEAR_CREDENTIALS = "EXTRA_CLEAR_CREDENTIALS"
|
|
||||||
|
|
||||||
// Special action to clear cache and/or clear credentials
|
// Special action to clear cache and/or clear credentials
|
||||||
fun restartApp(activity: Activity, clearCache: Boolean = false, clearCredentials: Boolean = false) {
|
fun restartApp(activity: Activity, args: MainActivityArgs) {
|
||||||
val intent = Intent(activity, MainActivity::class.java)
|
val intent = Intent(activity, MainActivity::class.java)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
|
||||||
intent.putExtra(EXTRA_CLEAR_CACHE, clearCache)
|
intent.putExtra(EXTRA_ARGS, args)
|
||||||
intent.putExtra(EXTRA_CLEAR_CREDENTIALS, clearCredentials)
|
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var args: MainActivityArgs
|
||||||
|
|
||||||
|
@Inject lateinit var notificationDrawerManager: NotificationDrawerManager
|
||||||
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
@Inject lateinit var sessionHolder: ActiveSessionHolder
|
||||||
@Inject lateinit var errorFormatter: ErrorFormatter
|
@Inject lateinit var errorFormatter: ErrorFormatter
|
||||||
|
|
||||||
|
@ -64,48 +84,75 @@ class MainActivity : VectorBaseActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val clearCache = intent.getBooleanExtra(EXTRA_CLEAR_CACHE, false)
|
args = parseArgs()
|
||||||
val clearCredentials = intent.getBooleanExtra(EXTRA_CLEAR_CREDENTIALS, false)
|
if (args.clearCredentials || args.isUserLoggedOut) {
|
||||||
|
clearNotifications()
|
||||||
|
}
|
||||||
// Handle some wanted cleanup
|
// Handle some wanted cleanup
|
||||||
if (clearCache || clearCredentials) {
|
if (args.clearCache || args.clearCredentials) {
|
||||||
doCleanUp(clearCache, clearCredentials)
|
doCleanUp()
|
||||||
} else {
|
} else {
|
||||||
start()
|
startNextActivityAndFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) {
|
private fun clearNotifications() {
|
||||||
|
// Dismiss all notifications
|
||||||
|
notificationDrawerManager.clearAllEvents()
|
||||||
|
notificationDrawerManager.persistInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseArgs(): MainActivityArgs {
|
||||||
|
val argsFromIntent: MainActivityArgs? = intent.getParcelableExtra(EXTRA_ARGS)
|
||||||
|
Timber.w("Starting MainActivity with $argsFromIntent")
|
||||||
|
|
||||||
|
return MainActivityArgs(
|
||||||
|
clearCache = argsFromIntent?.clearCache ?: false,
|
||||||
|
clearCredentials = argsFromIntent?.clearCredentials ?: false,
|
||||||
|
isUserLoggedOut = argsFromIntent?.isUserLoggedOut ?: false,
|
||||||
|
isSoftLogout = argsFromIntent?.isSoftLogout ?: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doCleanUp() {
|
||||||
val session = sessionHolder.getSafeActiveSession()
|
val session = sessionHolder.getSafeActiveSession()
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
start()
|
startNextActivityAndFinish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
clearCredentials -> session.signOut(object : MatrixCallback<Unit> {
|
args.clearCredentials -> session.signOut(
|
||||||
override fun onSuccess(data: Unit) {
|
!args.isUserLoggedOut,
|
||||||
Timber.w("SIGN_OUT: success, start app")
|
object : MatrixCallback<Unit> {
|
||||||
sessionHolder.clearActiveSession()
|
override fun onSuccess(data: Unit) {
|
||||||
doLocalCleanupAndStart()
|
Timber.w("SIGN_OUT: success, start app")
|
||||||
}
|
sessionHolder.clearActiveSession()
|
||||||
|
doLocalCleanupAndStart()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
displayError(failure, clearCache, clearCredentials)
|
displayError(failure)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
clearCache -> session.clearCache(object : MatrixCallback<Unit> {
|
args.clearCache -> session.clearCache(
|
||||||
override fun onSuccess(data: Unit) {
|
object : MatrixCallback<Unit> {
|
||||||
session.startSyncing(applicationContext)
|
override fun onSuccess(data: Unit) {
|
||||||
doLocalCleanupAndStart()
|
session.startSyncing(applicationContext)
|
||||||
}
|
doLocalCleanupAndStart()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
displayError(failure, clearCache, clearCredentials)
|
displayError(failure)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
|
||||||
|
// No op here
|
||||||
|
Timber.w("Ignoring invalid token global error")
|
||||||
|
}
|
||||||
|
|
||||||
private fun doLocalCleanupAndStart() {
|
private fun doLocalCleanupAndStart() {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
// On UI Thread
|
// On UI Thread
|
||||||
|
@ -119,24 +166,43 @@ class MainActivity : VectorBaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
start()
|
startNextActivityAndFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun displayError(failure: Throwable, clearCache: Boolean, clearCredentials: Boolean) {
|
private fun displayError(failure: Throwable) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(R.string.dialog_title_error)
|
.setTitle(R.string.dialog_title_error)
|
||||||
.setMessage(errorFormatter.toHumanReadable(failure))
|
.setMessage(errorFormatter.toHumanReadable(failure))
|
||||||
.setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp(clearCache, clearCredentials) }
|
.setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() }
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> start() }
|
.setNegativeButton(R.string.cancel) { _, _ -> startNextActivityAndFinish() }
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start() {
|
private fun startNextActivityAndFinish() {
|
||||||
val intent = if (sessionHolder.hasActiveSession()) {
|
val intent = when {
|
||||||
HomeActivity.newIntent(this)
|
args.clearCredentials
|
||||||
} else {
|
&& !args.isUserLoggedOut ->
|
||||||
LoginActivity.newIntent(this, null)
|
// User has explicitly asked to log out
|
||||||
|
LoginActivity.newIntent(this, null)
|
||||||
|
args.isSoftLogout ->
|
||||||
|
// The homeserver has invalidated the token, with a soft logout
|
||||||
|
SoftLogoutActivity.newIntent(this)
|
||||||
|
args.isUserLoggedOut ->
|
||||||
|
// the homeserver has invalidated the token (password changed, device deleted, other security reason
|
||||||
|
SignedOutActivity.newIntent(this)
|
||||||
|
sessionHolder.hasActiveSession() ->
|
||||||
|
// We have a session.
|
||||||
|
// Check it can be opened
|
||||||
|
if (sessionHolder.getActiveSession().isOpenable) {
|
||||||
|
HomeActivity.newIntent(this)
|
||||||
|
} else {
|
||||||
|
// The token is still invalid
|
||||||
|
SoftLogoutActivity.newIntent(this)
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
// First start, or no active session
|
||||||
|
LoginActivity.newIntent(this, null)
|
||||||
}
|
}
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
|
|
|
@ -18,11 +18,12 @@ package im.vector.riotx.features.autocomplete.user
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
import com.airbnb.epoxy.TypedEpoxyController
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
import im.vector.riotx.features.autocomplete.AutocompleteClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AutocompleteUserController @Inject constructor(): TypedEpoxyController<List<User>>() {
|
class AutocompleteUserController @Inject constructor() : TypedEpoxyController<List<User>>() {
|
||||||
|
|
||||||
var listener: AutocompleteClickListener<User>? = null
|
var listener: AutocompleteClickListener<User>? = null
|
||||||
|
|
||||||
|
@ -35,9 +36,7 @@ class AutocompleteUserController @Inject constructor(): TypedEpoxyController<Lis
|
||||||
data.forEach { user ->
|
data.forEach { user ->
|
||||||
autocompleteUserItem {
|
autocompleteUserItem {
|
||||||
id(user.userId)
|
id(user.userId)
|
||||||
userId(user.userId)
|
matrixItem(user.toMatrixItem())
|
||||||
name(user.displayName)
|
|
||||||
avatarUrl(user.avatarUrl)
|
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
clickListener { _ ->
|
clickListener { _ ->
|
||||||
listener?.onItemClick(user)
|
listener?.onItemClick(user)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
@ -30,15 +31,13 @@ import im.vector.riotx.features.home.AvatarRenderer
|
||||||
abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() {
|
abstract class AutocompleteUserItem : VectorEpoxyModel<AutocompleteUserItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute var name: String? = null
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute var userId: String = ""
|
|
||||||
@EpoxyAttribute var avatarUrl: String? = null
|
|
||||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
holder.view.setOnClickListener(clickListener)
|
holder.view.setOnClickListener(clickListener)
|
||||||
holder.nameView.text = name
|
holder.nameView.text = matrixItem.getBestName()
|
||||||
avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import androidx.lifecycle.Observer
|
||||||
import butterknife.BindView
|
import butterknife.BindView
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
@ -57,10 +59,10 @@ class SASVerificationIncomingFragment @Inject constructor(
|
||||||
otherDeviceTextView.text = viewModel.otherDeviceId
|
otherDeviceTextView.text = viewModel.otherDeviceId
|
||||||
|
|
||||||
viewModel.otherUser?.let {
|
viewModel.otherUser?.let {
|
||||||
avatarRenderer.render(it, avatarImageView)
|
avatarRenderer.render(it.toMatrixItem(), avatarImageView)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// Fallback to what we know
|
// Fallback to what we know
|
||||||
avatarRenderer.render(null, viewModel.otherUserId ?: "", viewModel.otherUserId, avatarImageView)
|
avatarRenderer.render(MatrixItem.UserItem(viewModel.otherUserId ?: "", viewModel.otherUserId), avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.transactionState.observe(viewLifecycleOwner, Observer {
|
viewModel.transactionState.observe(viewLifecycleOwner, Observer {
|
||||||
|
|
|
@ -27,10 +27,7 @@ import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.DrawableImageViewTarget
|
import com.bumptech.glide.request.target.DrawableImageViewTarget
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
|
||||||
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
|
||||||
import im.vector.riotx.R
|
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
import im.vector.riotx.core.glide.GlideRequest
|
import im.vector.riotx.core.glide.GlideRequest
|
||||||
|
@ -45,76 +42,42 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val THUMBNAIL_SIZE = 250
|
private const val THUMBNAIL_SIZE = 250
|
||||||
|
|
||||||
private val AVATAR_COLOR_LIST = listOf(
|
|
||||||
R.color.riotx_avatar_fill_1,
|
|
||||||
R.color.riotx_avatar_fill_2,
|
|
||||||
R.color.riotx_avatar_fill_3
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(roomSummary: RoomSummary, imageView: ImageView) {
|
fun render(matrixItem: MatrixItem, imageView: ImageView) {
|
||||||
render(roomSummary.avatarUrl, roomSummary.roomId, roomSummary.displayName, imageView)
|
render(imageView.context,
|
||||||
}
|
GlideApp.with(imageView),
|
||||||
|
matrixItem,
|
||||||
@UiThread
|
DrawableImageViewTarget(imageView))
|
||||||
fun render(user: User, imageView: ImageView) {
|
|
||||||
render(imageView.context, GlideApp.with(imageView), user.avatarUrl, user.userId, user.displayName, DrawableImageViewTarget(imageView))
|
|
||||||
}
|
|
||||||
|
|
||||||
@UiThread
|
|
||||||
fun render(avatarUrl: String?, identifier: String, name: String?, imageView: ImageView) {
|
|
||||||
render(imageView.context, GlideApp.with(imageView), avatarUrl, identifier, name, DrawableImageViewTarget(imageView))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(context: Context,
|
fun render(context: Context,
|
||||||
glideRequest: GlideRequests,
|
glideRequest: GlideRequests,
|
||||||
avatarUrl: String?,
|
matrixItem: MatrixItem,
|
||||||
identifier: String,
|
|
||||||
name: String?,
|
|
||||||
target: Target<Drawable>) {
|
target: Target<Drawable>) {
|
||||||
val displayName = if (name.isNullOrBlank()) {
|
val placeholder = getPlaceholderDrawable(context, matrixItem)
|
||||||
identifier
|
buildGlideRequest(glideRequest, matrixItem.avatarUrl)
|
||||||
} else {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
val placeholder = getPlaceholderDrawable(context, identifier, displayName)
|
|
||||||
buildGlideRequest(glideRequest, avatarUrl)
|
|
||||||
.placeholder(placeholder)
|
.placeholder(placeholder)
|
||||||
.into(target)
|
.into(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun getPlaceholderDrawable(context: Context, identifier: String, text: String): Drawable {
|
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
|
||||||
val avatarColor = ContextCompat.getColor(context, getColorFromUserId(identifier))
|
val avatarColor = when (matrixItem) {
|
||||||
return if (text.isEmpty()) {
|
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
|
||||||
TextDrawable.builder().buildRound("", avatarColor)
|
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
|
||||||
} else {
|
|
||||||
val firstLetter = text.firstLetterOfDisplayName()
|
|
||||||
TextDrawable.builder()
|
|
||||||
.beginConfig()
|
|
||||||
.bold()
|
|
||||||
.endConfig()
|
|
||||||
.buildRound(firstLetter, avatarColor)
|
|
||||||
}
|
}
|
||||||
|
return TextDrawable.builder()
|
||||||
|
.beginConfig()
|
||||||
|
.bold()
|
||||||
|
.endConfig()
|
||||||
|
.buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRIVATE API *********************************************************************************
|
// PRIVATE API *********************************************************************************
|
||||||
|
|
||||||
// private fun getAvatarColor(text: String? = null): Int {
|
|
||||||
// var colorIndex: Long = 0
|
|
||||||
// if (!text.isNullOrEmpty()) {
|
|
||||||
// var sum: Long = 0
|
|
||||||
// for (i in 0 until text.length) {
|
|
||||||
// sum += text[i].toLong()
|
|
||||||
// }
|
|
||||||
// colorIndex = sum % AVATAR_COLOR_LIST.size
|
|
||||||
// }
|
|
||||||
// return AVATAR_COLOR_LIST[colorIndex.toInt()]
|
|
||||||
// }
|
|
||||||
|
|
||||||
private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
||||||
val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver()
|
val resolvedUrl = activeSessionHolder.getActiveSession().contentUrlResolver()
|
||||||
.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationItemView
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
import com.google.android.material.bottomnavigation.BottomNavigationMenuView
|
||||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.commitTransactionNow
|
import im.vector.riotx.core.extensions.commitTransactionNow
|
||||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||||
|
@ -74,12 +75,7 @@ class HomeDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private fun onGroupChange(groupSummary: GroupSummary?) {
|
private fun onGroupChange(groupSummary: GroupSummary?) {
|
||||||
groupSummary?.let {
|
groupSummary?.let {
|
||||||
avatarRenderer.render(
|
avatarRenderer.render(it.toMatrixItem(), groupToolbarAvatarImageView)
|
||||||
it.avatarUrl,
|
|
||||||
it.groupId,
|
|
||||||
it.displayName,
|
|
||||||
groupToolbarAvatarImageView
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +151,7 @@ class HomeDetailFragment @Inject constructor(
|
||||||
bottomNavigationView.selectedItemId = when (displayMode) {
|
bottomNavigationView.selectedItemId = when (displayMode) {
|
||||||
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
RoomListDisplayMode.PEOPLE -> R.id.bottom_action_people
|
||||||
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
RoomListDisplayMode.ROOMS -> R.id.bottom_action_rooms
|
||||||
else -> R.id.bottom_action_home
|
else -> R.id.bottom_action_home
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,5 +34,5 @@ data class HomeDetailViewState(
|
||||||
val notificationHighlightPeople: Boolean = false,
|
val notificationHighlightPeople: Boolean = false,
|
||||||
val notificationCountRooms: Int = 0,
|
val notificationCountRooms: Int = 0,
|
||||||
val notificationHighlightRooms: Boolean = false,
|
val notificationHighlightRooms: Boolean = false,
|
||||||
val syncState: SyncState = SyncState.IDLE
|
val syncState: SyncState = SyncState.Idle
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.riotx.features.home
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.observeK
|
import im.vector.riotx.core.extensions.observeK
|
||||||
import im.vector.riotx.core.extensions.replaceChildFragment
|
import im.vector.riotx.core.extensions.replaceChildFragment
|
||||||
|
@ -42,7 +43,7 @@ class HomeDrawerFragment @Inject constructor(
|
||||||
session.liveUser(session.myUserId).observeK(this) { optionalUser ->
|
session.liveUser(session.myUserId).observeK(this) { optionalUser ->
|
||||||
val user = optionalUser?.getOrNull()
|
val user = optionalUser?.getOrNull()
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView)
|
avatarRenderer.render(user.toMatrixItem(), homeDrawerHeaderAvatarView)
|
||||||
homeDrawerUsernameView.text = user.displayName
|
homeDrawerUsernameView.text = user.displayName
|
||||||
homeDrawerUserIdView.text = user.userId
|
homeDrawerUserIdView.text = user.userId
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.riotx.features.home
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkData
|
|
||||||
import im.vector.matrix.android.api.permalinks.PermalinkParser
|
|
||||||
import im.vector.matrix.android.api.session.Session
|
|
||||||
import im.vector.riotx.features.navigation.Navigator
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class PermalinkHandler @Inject constructor(private val session: Session,
|
|
||||||
private val navigator: Navigator) {
|
|
||||||
|
|
||||||
fun launch(context: Context, deepLink: String?, navigateToRoomInterceptor: NavigateToRoomInterceptor? = null): Boolean {
|
|
||||||
val uri = deepLink?.let { Uri.parse(it) }
|
|
||||||
return launch(context, uri, navigateToRoomInterceptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun launch(context: Context, deepLink: Uri?, navigateToRoomInterceptor: NavigateToRoomInterceptor? = null): Boolean {
|
|
||||||
if (deepLink == null) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (val permalinkData = PermalinkParser.parse(deepLink)) {
|
|
||||||
is PermalinkData.EventLink -> {
|
|
||||||
if (navigateToRoomInterceptor?.navToRoom(permalinkData.roomIdOrAlias, permalinkData.eventId) != true) {
|
|
||||||
openRoom(context, permalinkData.roomIdOrAlias, permalinkData.eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
is PermalinkData.RoomLink -> {
|
|
||||||
if (navigateToRoomInterceptor?.navToRoom(permalinkData.roomIdOrAlias) != true) {
|
|
||||||
openRoom(context, permalinkData.roomIdOrAlias)
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
is PermalinkData.GroupLink -> {
|
|
||||||
navigator.openGroupDetail(permalinkData.groupId, context)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
is PermalinkData.UserLink -> {
|
|
||||||
navigator.openUserDetail(permalinkData.userId, context)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
is PermalinkData.FallbackLink -> {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open room either joined, or not unknown
|
|
||||||
*/
|
|
||||||
private fun openRoom(context: Context, roomIdOrAlias: String, eventId: String? = null) {
|
|
||||||
if (session.getRoom(roomIdOrAlias) != null) {
|
|
||||||
navigator.openRoom(context, roomIdOrAlias, eventId)
|
|
||||||
} else {
|
|
||||||
navigator.openNotJoinedRoom(context, roomIdOrAlias, eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NavigateToRoomInterceptor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return true if the navigation has been intercepted
|
|
||||||
*/
|
|
||||||
fun navToRoom(roomId: String, eventId: String? = null): Boolean
|
|
||||||
}
|
|
|
@ -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.riotx.features.home
|
||||||
|
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
import im.vector.riotx.R
|
||||||
|
|
||||||
|
@ColorRes
|
||||||
|
fun getColorFromRoomId(roomId: String?): Int {
|
||||||
|
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) {
|
||||||
|
1 -> R.color.riotx_avatar_fill_2
|
||||||
|
2 -> R.color.riotx_avatar_fill_3
|
||||||
|
else -> R.color.riotx_avatar_fill_1
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,28 +22,18 @@ import kotlin.math.abs
|
||||||
|
|
||||||
@ColorRes
|
@ColorRes
|
||||||
fun getColorFromUserId(userId: String?): Int {
|
fun getColorFromUserId(userId: String?): Int {
|
||||||
if (userId.isNullOrBlank()) {
|
|
||||||
return R.color.riotx_username_1
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash = 0
|
var hash = 0
|
||||||
var i = 0
|
|
||||||
var chr: Char
|
|
||||||
|
|
||||||
while (i < userId.length) {
|
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
|
||||||
chr = userId[i]
|
|
||||||
hash = (hash shl 5) - hash + chr.toInt()
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (abs(hash) % 8 + 1) {
|
return when (abs(hash) % 8) {
|
||||||
1 -> R.color.riotx_username_1
|
1 -> R.color.riotx_username_2
|
||||||
2 -> R.color.riotx_username_2
|
2 -> R.color.riotx_username_3
|
||||||
3 -> R.color.riotx_username_3
|
3 -> R.color.riotx_username_4
|
||||||
4 -> R.color.riotx_username_4
|
4 -> R.color.riotx_username_5
|
||||||
5 -> R.color.riotx_username_5
|
5 -> R.color.riotx_username_6
|
||||||
6 -> R.color.riotx_username_6
|
6 -> R.color.riotx_username_7
|
||||||
7 -> R.color.riotx_username_7
|
7 -> R.color.riotx_username_8
|
||||||
else -> R.color.riotx_username_8
|
else -> R.color.riotx_username_1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.core.content.ContextCompat
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
@ -34,22 +35,20 @@ import im.vector.riotx.features.home.AvatarRenderer
|
||||||
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {
|
abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute var name: String? = null
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute var userId: String = ""
|
|
||||||
@EpoxyAttribute var avatarUrl: String? = null
|
|
||||||
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
@EpoxyAttribute var clickListener: View.OnClickListener? = null
|
||||||
@EpoxyAttribute var selected: Boolean = false
|
@EpoxyAttribute var selected: Boolean = false
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
holder.view.setOnClickListener(clickListener)
|
holder.view.setOnClickListener(clickListener)
|
||||||
// If name is empty, use userId as name and force it being centered
|
// If name is empty, use userId as name and force it being centered
|
||||||
if (name.isNullOrEmpty()) {
|
if (matrixItem.displayName.isNullOrEmpty()) {
|
||||||
holder.userIdView.visibility = View.GONE
|
holder.userIdView.visibility = View.GONE
|
||||||
holder.nameView.text = userId
|
holder.nameView.text = matrixItem.id
|
||||||
} else {
|
} else {
|
||||||
holder.userIdView.visibility = View.VISIBLE
|
holder.userIdView.visibility = View.VISIBLE
|
||||||
holder.nameView.text = name
|
holder.nameView.text = matrixItem.displayName
|
||||||
holder.userIdView.text = userId
|
holder.userIdView.text = matrixItem.id
|
||||||
}
|
}
|
||||||
renderSelection(holder, selected)
|
renderSelection(holder, selected)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +61,7 @@ abstract class CreateDirectRoomUserItem : VectorEpoxyModel<CreateDirectRoomUserI
|
||||||
holder.avatarImageView.setImageDrawable(backgroundDrawable)
|
holder.avatarImageView.setImageDrawable(backgroundDrawable)
|
||||||
} else {
|
} else {
|
||||||
holder.avatarCheckedImageView.visibility = View.GONE
|
holder.avatarCheckedImageView.visibility = View.GONE
|
||||||
avatarRenderer.render(avatarUrl, userId, name, holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.matrix.rx.rx
|
import im.vector.matrix.rx.rx
|
||||||
import im.vector.riotx.core.extensions.postLiveEvent
|
import im.vector.riotx.core.extensions.postLiveEvent
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
@ -142,7 +142,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||||
session.rx()
|
session.rx()
|
||||||
.searchUsersDirectory(search, 50, emptySet())
|
.searchUsersDirectory(search, 50, emptySet())
|
||||||
.map { users ->
|
.map { users ->
|
||||||
users.sortedBy { it.displayName.firstLetterOfDisplayName() }
|
users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stream.toAsync {
|
stream.toAsync {
|
||||||
|
|
|
@ -19,9 +19,13 @@
|
||||||
package im.vector.riotx.features.home.createdirect
|
package im.vector.riotx.features.home.createdirect
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import com.airbnb.mvrx.*
|
import com.airbnb.mvrx.Fail
|
||||||
|
import com.airbnb.mvrx.Loading
|
||||||
|
import com.airbnb.mvrx.Success
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
import im.vector.riotx.core.epoxy.errorWithRetryItem
|
||||||
import im.vector.riotx.core.epoxy.loadingItem
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
|
@ -94,9 +98,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session,
|
||||||
createDirectRoomUserItem {
|
createDirectRoomUserItem {
|
||||||
id(user.userId)
|
id(user.userId)
|
||||||
selected(isSelected)
|
selected(isSelected)
|
||||||
userId(user.userId)
|
matrixItem(user.toMatrixItem())
|
||||||
name(user.displayName)
|
|
||||||
avatarUrl(user.avatarUrl)
|
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
clickListener { _ ->
|
clickListener { _ ->
|
||||||
callback?.onItemClick(user)
|
callback?.onItemClick(user)
|
||||||
|
|
|
@ -23,7 +23,7 @@ import com.airbnb.mvrx.Incomplete
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
import im.vector.matrix.android.internal.util.firstLetterOfDisplayName
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.EmptyItem_
|
import im.vector.riotx.core.epoxy.EmptyItem_
|
||||||
import im.vector.riotx.core.epoxy.loadingItem
|
import im.vector.riotx.core.epoxy.loadingItem
|
||||||
|
@ -68,9 +68,7 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
CreateDirectRoomUserItem_()
|
CreateDirectRoomUserItem_()
|
||||||
.id(item.userId)
|
.id(item.userId)
|
||||||
.selected(isSelected)
|
.selected(isSelected)
|
||||||
.userId(item.userId)
|
.matrixItem(item.toMatrixItem())
|
||||||
.name(item.displayName)
|
|
||||||
.avatarUrl(item.avatarUrl)
|
|
||||||
.avatarRenderer(avatarRenderer)
|
.avatarRenderer(avatarRenderer)
|
||||||
.clickListener { _ ->
|
.clickListener { _ ->
|
||||||
callback?.onItemClick(item)
|
callback?.onItemClick(item)
|
||||||
|
@ -87,8 +85,8 @@ class KnownUsersController @Inject constructor(private val session: Session,
|
||||||
var lastFirstLetter: String? = null
|
var lastFirstLetter: String? = null
|
||||||
for (model in models) {
|
for (model in models) {
|
||||||
if (model is CreateDirectRoomUserItem) {
|
if (model is CreateDirectRoomUserItem) {
|
||||||
if (model.userId == session.myUserId) continue
|
if (model.matrixItem.id == session.myUserId) continue
|
||||||
val currentFirstLetter = model.name.firstLetterOfDisplayName()
|
val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
|
||||||
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
|
||||||
lastFirstLetter = currentFirstLetter
|
lastFirstLetter = currentFirstLetter
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ import im.vector.riotx.core.utils.LiveEvent
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
|
|
||||||
const val ALL_COMMUNITIES_GROUP_ID = "ALL_COMMUNITIES_GROUP_ID"
|
const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID"
|
||||||
|
|
||||||
class GroupListViewModel @AssistedInject constructor(@Assisted initialState: GroupListViewState,
|
class GroupListViewModel @AssistedInject constructor(@Assisted initialState: GroupListViewState,
|
||||||
private val selectedGroupStore: SelectedGroupDataSource,
|
private val selectedGroupStore: SelectedGroupDataSource,
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.group
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -49,10 +50,8 @@ class GroupSummaryController @Inject constructor(private val avatarRenderer: Ava
|
||||||
groupSummaryItem {
|
groupSummaryItem {
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
id(groupSummary.groupId)
|
id(groupSummary.groupId)
|
||||||
groupId(groupSummary.groupId)
|
matrixItem(groupSummary.toMatrixItem())
|
||||||
groupName(groupSummary.displayName)
|
|
||||||
selected(isSelected)
|
selected(isSelected)
|
||||||
avatarUrl(groupSummary.avatarUrl)
|
|
||||||
listener { callback?.onGroupSelected(groupSummary) }
|
listener { callback?.onGroupSelected(groupSummary) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
@ -30,18 +31,16 @@ import im.vector.riotx.features.home.AvatarRenderer
|
||||||
abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
|
abstract class GroupSummaryItem : VectorEpoxyModel<GroupSummaryItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var groupName: CharSequence
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute lateinit var groupId: String
|
|
||||||
@EpoxyAttribute var avatarUrl: String? = null
|
|
||||||
@EpoxyAttribute var selected: Boolean = false
|
@EpoxyAttribute var selected: Boolean = false
|
||||||
@EpoxyAttribute var listener: (() -> Unit)? = null
|
@EpoxyAttribute var listener: (() -> Unit)? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.rootView.setOnClickListener { listener?.invoke() }
|
holder.rootView.setOnClickListener { listener?.invoke() }
|
||||||
holder.groupNameView.text = groupName
|
holder.groupNameView.text = matrixItem.displayName
|
||||||
holder.rootView.isChecked = selected
|
holder.rootView.isChecked = selected
|
||||||
avatarRenderer.render(avatarUrl, groupId, groupName.toString(), holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : VectorEpoxyHolder() {
|
class Holder : VectorEpoxyHolder() {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.breadcrumbs
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.core.utils.DebouncedClickListener
|
import im.vector.riotx.core.utils.DebouncedClickListener
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -52,9 +53,7 @@ class BreadcrumbsController @Inject constructor(
|
||||||
breadcrumbsItem {
|
breadcrumbsItem {
|
||||||
id(it.roomId)
|
id(it.roomId)
|
||||||
avatarRenderer(avatarRenderer)
|
avatarRenderer(avatarRenderer)
|
||||||
roomId(it.roomId)
|
matrixItem(it.toMatrixItem())
|
||||||
roomName(it.displayName)
|
|
||||||
avatarUrl(it.avatarUrl)
|
|
||||||
unreadNotificationCount(it.notificationCount)
|
unreadNotificationCount(it.notificationCount)
|
||||||
showHighlighted(it.highlightCount > 0)
|
showHighlighted(it.highlightCount > 0)
|
||||||
hasUnreadMessage(it.hasUnreadMessages)
|
hasUnreadMessage(it.hasUnreadMessages)
|
||||||
|
|
|
@ -48,6 +48,7 @@ class BreadcrumbsFragment @Inject constructor(
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
breadcrumbsRecyclerView.cleanup()
|
breadcrumbsRecyclerView.cleanup()
|
||||||
|
breadcrumbsController.listener = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +57,7 @@ class BreadcrumbsFragment @Inject constructor(
|
||||||
breadcrumbsController.listener = this
|
breadcrumbsController.listener = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Use invalidate() ?
|
||||||
private fun renderState(state: BreadcrumbsViewState) {
|
private fun renderState(state: BreadcrumbsViewState) {
|
||||||
breadcrumbsController.update(state)
|
breadcrumbsController.update(state)
|
||||||
}
|
}
|
||||||
|
@ -65,4 +67,8 @@ class BreadcrumbsFragment @Inject constructor(
|
||||||
override fun onBreadcrumbClicked(roomId: String) {
|
override fun onBreadcrumbClicked(roomId: String) {
|
||||||
sharedActionViewModel.post(RoomDetailSharedAction.SwitchToRoom(roomId))
|
sharedActionViewModel.post(RoomDetailSharedAction.SwitchToRoom(roomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun scrollToTop() {
|
||||||
|
breadcrumbsRecyclerView.scrollToPosition(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.widget.ImageView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
import im.vector.riotx.core.epoxy.VectorEpoxyModel
|
||||||
|
@ -32,9 +33,7 @@ import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
|
||||||
abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
@EpoxyAttribute lateinit var roomId: String
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute lateinit var roomName: CharSequence
|
|
||||||
@EpoxyAttribute var avatarUrl: String? = null
|
|
||||||
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
@EpoxyAttribute var unreadNotificationCount: Int = 0
|
||||||
@EpoxyAttribute var showHighlighted: Boolean = false
|
@EpoxyAttribute var showHighlighted: Boolean = false
|
||||||
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
@EpoxyAttribute var hasUnreadMessage: Boolean = false
|
||||||
|
@ -45,7 +44,7 @@ abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
holder.rootView.setOnClickListener(itemClickListener)
|
holder.rootView.setOnClickListener(itemClickListener)
|
||||||
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
|
holder.unreadIndentIndicator.isVisible = hasUnreadMessage
|
||||||
avatarRenderer.render(avatarUrl, roomId, roomName.toString(), holder.avatarImageView)
|
avatarRenderer.render(matrixItem, holder.avatarImageView)
|
||||||
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
|
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
|
||||||
holder.draftIndentIndicator.isVisible = hasDraft
|
holder.draftIndentIndicator.isVisible = hasDraft
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,9 +86,19 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
|
||||||
override fun onDrawerStateChanged(newState: Int) {
|
override fun onDrawerStateChanged(newState: Int) {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
|
||||||
|
if (!drawerLayout.isDrawerOpen(GravityCompat.START) && newState == DrawerLayout.STATE_DRAGGING) {
|
||||||
|
// User is starting to open the drawer, scroll the list to op
|
||||||
|
scrollBreadcrumbsToTop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun scrollBreadcrumbsToTop() {
|
||||||
|
supportFragmentManager.fragments.filterIsInstance<BreadcrumbsFragment>()
|
||||||
|
.forEach { it.scrollToTop() }
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||||
drawerLayout.closeDrawer(GravityCompat.START)
|
drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
|
|
|
@ -66,10 +66,11 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||||
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
|
||||||
import im.vector.matrix.android.api.session.user.model.User
|
import im.vector.matrix.android.api.session.user.model.User
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
|
import im.vector.matrix.android.api.util.toMatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.dialogs.withColoredButton
|
import im.vector.riotx.core.dialogs.withColoredButton
|
||||||
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
|
||||||
import im.vector.riotx.core.extensions.*
|
import im.vector.riotx.core.extensions.*
|
||||||
import im.vector.riotx.core.files.addEntryToDownloadManager
|
import im.vector.riotx.core.files.addEntryToDownloadManager
|
||||||
import im.vector.riotx.core.glide.GlideApp
|
import im.vector.riotx.core.glide.GlideApp
|
||||||
|
@ -85,8 +86,8 @@ import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy
|
||||||
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter
|
||||||
import im.vector.riotx.features.command.Command
|
import im.vector.riotx.features.command.Command
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.NavigateToRoomInterceptor
|
import im.vector.riotx.features.permalink.NavigateToRoomInterceptor
|
||||||
import im.vector.riotx.features.home.PermalinkHandler
|
import im.vector.riotx.features.permalink.PermalinkHandler
|
||||||
import im.vector.riotx.features.home.getColorFromUserId
|
import im.vector.riotx.features.home.getColorFromUserId
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerAction
|
||||||
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
import im.vector.riotx.features.home.room.detail.composer.TextComposerView
|
||||||
|
@ -112,6 +113,8 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
|
||||||
import im.vector.riotx.features.settings.VectorPreferences
|
import im.vector.riotx.features.settings.VectorPreferences
|
||||||
import im.vector.riotx.features.share.SharedData
|
import im.vector.riotx.features.share.SharedData
|
||||||
import im.vector.riotx.features.themes.ThemeUtils
|
import im.vector.riotx.features.themes.ThemeUtils
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||||
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
|
import kotlinx.android.synthetic.main.merge_composer_layout.view.*
|
||||||
|
@ -141,7 +144,6 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private val notificationDrawerManager: NotificationDrawerManager,
|
private val notificationDrawerManager: NotificationDrawerManager,
|
||||||
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
|
||||||
val textComposerViewModelFactory: TextComposerViewModel.Factory,
|
val textComposerViewModelFactory: TextComposerViewModel.Factory,
|
||||||
private val errorFormatter: ErrorFormatter,
|
|
||||||
private val eventHtmlRenderer: EventHtmlRenderer,
|
private val eventHtmlRenderer: EventHtmlRenderer,
|
||||||
private val vectorPreferences: VectorPreferences
|
private val vectorPreferences: VectorPreferences
|
||||||
) :
|
) :
|
||||||
|
@ -410,9 +412,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
|
composerLayout.sendButton.setContentDescription(getString(descriptionRes))
|
||||||
|
|
||||||
avatarRenderer.render(
|
avatarRenderer.render(
|
||||||
event.senderAvatar,
|
MatrixItem.UserItem(event.root.senderId ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
|
||||||
event.root.senderId ?: "",
|
|
||||||
event.getDisambiguatedDisplayName(),
|
|
||||||
composerLayout.composerRelatedMessageAvatar
|
composerLayout.composerRelatedMessageAvatar
|
||||||
)
|
)
|
||||||
composerLayout.expand {
|
composerLayout.expand {
|
||||||
|
@ -601,20 +601,19 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the word by its completion
|
// Replace the word by its completion
|
||||||
val displayName = item.displayName ?: item.userId
|
val matrixItem = item.toMatrixItem()
|
||||||
|
val displayName = matrixItem.getBestName()
|
||||||
|
|
||||||
// with a trailing space
|
// with a trailing space
|
||||||
editable.replace(startIndex, endIndex, "$displayName ")
|
editable.replace(startIndex, endIndex, "$displayName ")
|
||||||
|
|
||||||
// Add the span
|
// Add the span
|
||||||
val user = session.getUser(item.userId)
|
|
||||||
val span = PillImageSpan(
|
val span = PillImageSpan(
|
||||||
glideRequests,
|
glideRequests,
|
||||||
avatarRenderer,
|
avatarRenderer,
|
||||||
requireContext(),
|
requireContext(),
|
||||||
item.userId,
|
matrixItem
|
||||||
user?.displayName ?: item.userId,
|
)
|
||||||
user?.avatarUrl)
|
|
||||||
span.bind(composerLayout.composerEditText)
|
span.bind(composerLayout.composerEditText)
|
||||||
|
|
||||||
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
@ -686,7 +685,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
inviteView.visibility = View.GONE
|
inviteView.visibility = View.GONE
|
||||||
val uid = session.myUserId
|
val uid = session.myUserId
|
||||||
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
|
val meMember = session.getRoom(state.roomId)?.getRoomMember(uid)
|
||||||
avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView)
|
avatarRenderer.render(MatrixItem.UserItem(uid, meMember?.displayName, meMember?.avatarUrl), composerLayout.composerAvatarImageView)
|
||||||
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
} else if (summary?.membership == Membership.INVITE && inviter != null) {
|
||||||
inviteView.visibility = View.VISIBLE
|
inviteView.visibility = View.VISIBLE
|
||||||
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
|
inviteView.render(inviter, VectorInviteView.Mode.LARGE)
|
||||||
|
@ -713,7 +712,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
activity?.finish()
|
activity?.finish()
|
||||||
} else {
|
} else {
|
||||||
roomToolbarTitleView.text = it.displayName
|
roomToolbarTitleView.text = it.displayName
|
||||||
avatarRenderer.render(it, roomToolbarAvatarImageView)
|
avatarRenderer.render(it.toMatrixItem(), roomToolbarAvatarImageView)
|
||||||
roomToolbarSubtitleView.setTextOrHide(it.topic)
|
roomToolbarSubtitleView.setTextOrHide(it.topic)
|
||||||
}
|
}
|
||||||
jumpToBottomView.count = it.notificationCount
|
jumpToBottomView.count = it.notificationCount
|
||||||
|
@ -854,30 +853,33 @@ class RoomDetailFragment @Inject constructor(
|
||||||
// TimelineEventController.Callback ************************************************************
|
// TimelineEventController.Callback ************************************************************
|
||||||
|
|
||||||
override fun onUrlClicked(url: String): Boolean {
|
override fun onUrlClicked(url: String): Boolean {
|
||||||
val managed = permalinkHandler.launch(requireActivity(), url, object : NavigateToRoomInterceptor {
|
permalinkHandler
|
||||||
override fun navToRoom(roomId: String, eventId: String?): Boolean {
|
.launch(requireActivity(), url, object : NavigateToRoomInterceptor {
|
||||||
// Same room?
|
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
|
||||||
if (roomId == roomDetailArgs.roomId) {
|
// Same room?
|
||||||
// Navigation to same room
|
if (roomId == roomDetailArgs.roomId) {
|
||||||
if (eventId == null) {
|
// Navigation to same room
|
||||||
showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room))
|
if (eventId == null) {
|
||||||
} else {
|
showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room))
|
||||||
// Highlight and scroll to this event
|
} else {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
|
// Highlight and scroll to this event
|
||||||
|
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Not handled
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { managed ->
|
||||||
|
if (!managed) {
|
||||||
|
// Open in external browser, in a new Tab
|
||||||
|
openUrlInExternalBrowser(requireContext(), url)
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
.disposeOnDestroyView()
|
||||||
// Not handled
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!managed) {
|
|
||||||
// Open in external browser, in a new Tab
|
|
||||||
openUrlInExternalBrowser(requireContext(), url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In fact it is always managed
|
// In fact it is always managed
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1025,12 +1027,15 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRoomCreateLinkClicked(url: String) {
|
override fun onRoomCreateLinkClicked(url: String) {
|
||||||
permalinkHandler.launch(requireContext(), url, object : NavigateToRoomInterceptor {
|
permalinkHandler
|
||||||
override fun navToRoom(roomId: String, eventId: String?): Boolean {
|
.launch(requireContext(), url, object : NavigateToRoomInterceptor {
|
||||||
requireActivity().finish()
|
override fun navToRoom(roomId: String?, eventId: String?): Boolean {
|
||||||
return false
|
requireActivity().finish()
|
||||||
}
|
return false
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
.subscribe()
|
||||||
|
.disposeOnDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {
|
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {
|
||||||
|
@ -1197,9 +1202,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
glideRequests,
|
glideRequests,
|
||||||
avatarRenderer,
|
avatarRenderer,
|
||||||
requireContext(),
|
requireContext(),
|
||||||
userId,
|
MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl)
|
||||||
displayName,
|
)
|
||||||
roomMember?.avatarUrl)
|
|
||||||
.also { it.bind(composerLayout.composerEditText) },
|
.also { it.bind(composerLayout.composerEditText) },
|
||||||
0,
|
0,
|
||||||
displayName.length,
|
displayName.length,
|
||||||
|
|
|
@ -58,7 +58,7 @@ data class RoomDetailViewState(
|
||||||
val isEncrypted: Boolean = false,
|
val isEncrypted: Boolean = false,
|
||||||
val tombstoneEvent: Event? = null,
|
val tombstoneEvent: Event? = null,
|
||||||
val tombstoneEventHandling: Async<String> = Uninitialized,
|
val tombstoneEventHandling: Async<String> = Uninitialized,
|
||||||
val syncState: SyncState = SyncState.IDLE,
|
val syncState: SyncState = SyncState.Idle,
|
||||||
val highlightedEventId: String? = null,
|
val highlightedEventId: String? = null,
|
||||||
val unreadState: UnreadState = UnreadState.Unknown,
|
val unreadState: UnreadState = UnreadState.Unknown,
|
||||||
val canShowJumpToReadMarker: Boolean = true
|
val canShowJumpToReadMarker: Boolean = true
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.core.view.isVisible
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import com.airbnb.epoxy.EpoxyModelWithHolder
|
import com.airbnb.epoxy.EpoxyModelWithHolder
|
||||||
|
import im.vector.matrix.android.api.util.MatrixItem
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
import im.vector.riotx.core.epoxy.VectorEpoxyHolder
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
|
@ -29,15 +30,13 @@ import im.vector.riotx.features.home.AvatarRenderer
|
||||||
@EpoxyModelClass(layout = R.layout.item_display_read_receipt)
|
@EpoxyModelClass(layout = R.layout.item_display_read_receipt)
|
||||||
abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptItem.Holder>() {
|
abstract class DisplayReadReceiptItem : EpoxyModelWithHolder<DisplayReadReceiptItem.Holder>() {
|
||||||
|
|
||||||
@EpoxyAttribute var name: String? = null
|
@EpoxyAttribute lateinit var matrixItem: MatrixItem
|
||||||
@EpoxyAttribute var userId: String = ""
|
|
||||||
@EpoxyAttribute var avatarUrl: String? = null
|
|
||||||
@EpoxyAttribute var timestamp: CharSequence? = null
|
@EpoxyAttribute var timestamp: CharSequence? = null
|
||||||
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
avatarRenderer.render(avatarUrl, userId, name, holder.avatarView)
|
avatarRenderer.render(matrixItem, holder.avatarView)
|
||||||
holder.displayNameView.text = name ?: userId
|
holder.displayNameView.text = matrixItem.getBestName()
|
||||||
timestamp?.let {
|
timestamp?.let {
|
||||||
holder.timestampView.text = it
|
holder.timestampView.text = it
|
||||||
holder.timestampView.isVisible = true
|
holder.timestampView.isVisible = true
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.riotx.core.date.VectorDateFormatter
|
import im.vector.riotx.core.date.VectorDateFormatter
|
||||||
import im.vector.riotx.features.home.AvatarRenderer
|
import im.vector.riotx.features.home.AvatarRenderer
|
||||||
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
|
import im.vector.riotx.features.home.room.detail.timeline.item.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,9 +37,7 @@ class DisplayReadReceiptsController @Inject constructor(private val dateFormatte
|
||||||
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
||||||
DisplayReadReceiptItem_()
|
DisplayReadReceiptItem_()
|
||||||
.id(it.userId)
|
.id(it.userId)
|
||||||
.userId(it.userId)
|
.matrixItem(it.toMatrixItem())
|
||||||
.avatarUrl(it.avatarUrl)
|
|
||||||
.name(it.displayName)
|
|
||||||
.avatarRenderer(avatarRender)
|
.avatarRenderer(avatarRender)
|
||||||
.timestamp(timestamp)
|
.timestamp(timestamp)
|
||||||
.addIf(session.myUserId != it.userId, this)
|
.addIf(session.myUserId != it.userId, this)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue