mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
Merge branch 'develop' into feature/fix_user_vs_room_member
This commit is contained in:
commit
5eb50750e2
94 changed files with 1265 additions and 512 deletions
|
@ -5,12 +5,20 @@ Features ✨:
|
||||||
-
|
-
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
|
- New room creation tile with quick action (#2346)
|
||||||
- Open an existing DM instead of creating a new one (#2319)
|
- Open an existing DM instead of creating a new one (#2319)
|
||||||
- Use RoomMember instead of User in the context of a Room.
|
- Use RoomMember instead of User in the context of a Room.
|
||||||
|
- Ask for explicit user consent to send their contact details to the identity server (#2375)
|
||||||
|
- Handle events of type "m.room.server_acl" (#890)
|
||||||
|
- Move "Enable Encryption" from room setting screen to room profile screen (#2394)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
|
- Exclude yourself when decorating rooms which are direct or don't have more than 2 users (#2370)
|
||||||
|
- F-Droid version: ensure timeout of sync request can be more than 60 seconds (#2169)
|
||||||
|
- Fix issue when restoring draft after sharing (#2287)
|
||||||
- Fix issue when updating the avatar of a room (new avatar vanishing)
|
- Fix issue when updating the avatar of a room (new avatar vanishing)
|
||||||
- Discard change dialog displayed by mistake when avatar has been updated
|
- Discard change dialog displayed by mistake when avatar has been updated
|
||||||
|
- Registration: annoying error message scares every new user when they add an email (#2391)
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583
|
distributionSha256Sum=22449f5231796abd892c98b2a07c9ceebe4688d192cd2d6763f8e3bf8acbedeb
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -68,8 +68,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
if (encryptedRoom) {
|
if (encryptedRoom) {
|
||||||
val room = aliceSession.getRoom(roomId)!!
|
val room = aliceSession.getRoom(roomId)!!
|
||||||
|
|
||||||
mTestHelper.doSync<Unit> {
|
mTestHelper.runBlockingTest {
|
||||||
room.enableEncryption(callback = it)
|
room.enableEncryption()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,7 @@ class SearchMessagesTest : InstrumentedTest {
|
||||||
commonTestHelper.await(lock)
|
commonTestHelper.await(lock)
|
||||||
|
|
||||||
lock = CountDownLatch(1)
|
lock = CountDownLatch(1)
|
||||||
|
val data = commonTestHelper.runBlockingTest {
|
||||||
aliceSession
|
aliceSession
|
||||||
.searchService()
|
.searchService()
|
||||||
.search(
|
.search(
|
||||||
|
@ -81,10 +82,9 @@ class SearchMessagesTest : InstrumentedTest {
|
||||||
beforeLimit = 10,
|
beforeLimit = 10,
|
||||||
orderByRecent = true,
|
orderByRecent = true,
|
||||||
nextBatch = null,
|
nextBatch = null,
|
||||||
roomId = aliceRoomId,
|
roomId = aliceRoomId
|
||||||
callback = object : MatrixCallback<SearchResult> {
|
)
|
||||||
override fun onSuccess(data: SearchResult) {
|
}
|
||||||
super.onSuccess(data)
|
|
||||||
assertTrue(data.results?.size == 2)
|
assertTrue(data.results?.size == 2)
|
||||||
assertTrue(
|
assertTrue(
|
||||||
data.results
|
data.results
|
||||||
|
@ -92,17 +92,6 @@ class SearchMessagesTest : InstrumentedTest {
|
||||||
(it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
|
(it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse()
|
||||||
}.orFalse()
|
}.orFalse()
|
||||||
)
|
)
|
||||||
lock.countDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
super.onFailure(failure)
|
|
||||||
fail(failure.localizedMessage)
|
|
||||||
lock.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)
|
|
||||||
|
|
||||||
aliceTimeline.removeAllListeners()
|
aliceTimeline.removeAllListeners()
|
||||||
cryptoTestData.cleanUp(commonTestHelper)
|
cryptoTestData.cleanUp(commonTestHelper)
|
||||||
|
|
|
@ -22,3 +22,8 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence {
|
||||||
else -> "$prefix$this"
|
else -> "$prefix$this"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a new line and then the provided string
|
||||||
|
*/
|
||||||
|
fun StringBuilder.appendNl(str: String) = append("\n").append(str)
|
||||||
|
|
|
@ -15,11 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.api.pushrules
|
package org.matrix.android.sdk.api.pushrules
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
||||||
import org.matrix.android.sdk.api.pushrules.rest.RuleSet
|
import org.matrix.android.sdk.api.pushrules.rest.RuleSet
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
interface PushRuleService {
|
interface PushRuleService {
|
||||||
/**
|
/**
|
||||||
|
@ -29,13 +27,13 @@ interface PushRuleService {
|
||||||
|
|
||||||
fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet
|
fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet
|
||||||
|
|
||||||
fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean)
|
||||||
|
|
||||||
fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun addPushRule(kind: RuleKind, pushRule: PushRule)
|
||||||
|
|
||||||
fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule)
|
||||||
|
|
||||||
fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun removePushRule(kind: RuleKind, pushRule: PushRule)
|
||||||
|
|
||||||
fun addPushRuleListener(listener: PushRuleListener)
|
fun addPushRuleListener(listener: PushRuleListener)
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.raw
|
package org.matrix.android.sdk.api.raw
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful methods to fetch raw data from the server. The access token will not be used to fetched the data
|
* Useful methods to fetch raw data from the server. The access token will not be used to fetched the data
|
||||||
*/
|
*/
|
||||||
|
@ -26,17 +23,15 @@ interface RawService {
|
||||||
/**
|
/**
|
||||||
* Get a URL, either from cache or from the remote server, depending on the cache strategy
|
* Get a URL, either from cache or from the remote server, depending on the cache strategy
|
||||||
*/
|
*/
|
||||||
fun getUrl(url: String,
|
suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String
|
||||||
rawCacheStrategy: RawCacheStrategy,
|
|
||||||
matrixCallback: MatrixCallback<String>): Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific case for the well-known file. Cache validity is 8 hours
|
* Specific case for the well-known file. Cache validity is 8 hours
|
||||||
*/
|
*/
|
||||||
fun getWellknown(userId: String, matrixCallback: MatrixCallback<String>): Cancelable
|
suspend fun getWellknown(userId: String): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all the cache data
|
* Clear all the cache data
|
||||||
*/
|
*/
|
||||||
fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable
|
suspend fun clearCache()
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ object EventType {
|
||||||
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups"
|
||||||
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events"
|
||||||
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
||||||
|
const val STATE_ROOM_SERVER_ACL = "m.room.server_acl"
|
||||||
|
|
||||||
// Call Events
|
// Call Events
|
||||||
const val CALL_INVITE = "m.call.invite"
|
const val CALL_INVITE = "m.call.invite"
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.group
|
package org.matrix.android.sdk.api.session.group
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to interact within a group.
|
* This interface defines methods to interact within a group.
|
||||||
*/
|
*/
|
||||||
|
@ -28,8 +25,7 @@ interface Group {
|
||||||
/**
|
/**
|
||||||
* This methods allows you to refresh data about this group. It will be reflected on the GroupSummary.
|
* This methods allows you to refresh data about this group. It will be reflected on the GroupSummary.
|
||||||
* The SDK also takes care of refreshing group data every hour.
|
* The SDK also takes care of refreshing group data every hour.
|
||||||
* @param callback : the matrix callback to be notified of success or failure
|
|
||||||
* @return a Cancelable to be able to cancel requests.
|
* @return a Cancelable to be able to cancel requests.
|
||||||
*/
|
*/
|
||||||
fun fetchGroupData(callback: MatrixCallback<Unit>): Cancelable
|
suspend fun fetchGroupData()
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,9 +92,29 @@ interface IdentityService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search MatrixId of users providing email and phone numbers
|
* Search MatrixId of users providing email and phone numbers
|
||||||
|
* Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure
|
||||||
|
* Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent]
|
||||||
|
* Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
|
||||||
*/
|
*/
|
||||||
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the current user consent for the current identity server, which has been stored using [setUserConsent].
|
||||||
|
* If [setUserConsent] has not been called, the returned value will be false.
|
||||||
|
* Note that if the identity server is changed, the user consent is reset to false.
|
||||||
|
* @return the value stored using [setUserConsent] or false if [setUserConsent] has never been called, or if the identity server
|
||||||
|
* has been changed
|
||||||
|
*/
|
||||||
|
fun getUserConsent(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user consent to the provided value. Application MUST explicitly ask for the user consent to send their private data
|
||||||
|
* (email and phone numbers) to the identity server.
|
||||||
|
* Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
|
||||||
|
* @param newValue true if the user explicitly give their consent, false if the user wants to revoke their consent.
|
||||||
|
*/
|
||||||
|
fun setUserConsent(newValue: Boolean)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the status of the current user's threePid
|
* Get the status of the current user's threePid
|
||||||
* A lookup will be performed, but also pending binding state will be restored
|
* A lookup will be performed, but also pending binding state will be restored
|
||||||
|
|
|
@ -24,6 +24,7 @@ sealed class IdentityServiceError : Failure.FeatureFailure() {
|
||||||
object NoIdentityServerConfigured : IdentityServiceError()
|
object NoIdentityServerConfigured : IdentityServiceError()
|
||||||
object TermsNotSignedException : IdentityServiceError()
|
object TermsNotSignedException : IdentityServiceError()
|
||||||
object BulkLookupSha256NotSupported : IdentityServiceError()
|
object BulkLookupSha256NotSupported : IdentityServiceError()
|
||||||
|
object UserConsentNotProvided : IdentityServiceError()
|
||||||
object BindingError : IdentityServiceError()
|
object BindingError : IdentityServiceError()
|
||||||
object NoCurrentBindingError : IdentityServiceError()
|
object NoCurrentBindingError : IdentityServiceError()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.api.session.permalinks
|
package org.matrix.android.sdk.api.session.permalinks
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MatrixLinkify take a piece of text and turns all of the
|
* MatrixLinkify take a piece of text and turns all of the
|
||||||
|
@ -35,7 +36,7 @@ object MatrixLinkify {
|
||||||
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
|
* I disable it because it mess up with pills, and even with pills, it does not work correctly:
|
||||||
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
|
* The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if (spannable.isEmpty()) {
|
if (spannable.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
|
@ -48,14 +49,21 @@ object MatrixLinkify {
|
||||||
val startPos = match.range.first
|
val startPos = match.range.first
|
||||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||||
val endPos = match.range.last + 1
|
val endPos = match.range.last + 1
|
||||||
val url = text.substring(match.range)
|
var url = text.substring(match.range)
|
||||||
|
if (MatrixPatterns.isUserId(url)
|
||||||
|
|| MatrixPatterns.isRoomAlias(url)
|
||||||
|
|| MatrixPatterns.isRoomId(url)
|
||||||
|
|| MatrixPatterns.isGroupId(url)
|
||||||
|
|| MatrixPatterns.isEventId(url)) {
|
||||||
|
url = PermalinkService.MATRIX_TO_URL_BASE + url
|
||||||
|
}
|
||||||
val span = MatrixPermalinkSpan(url, callback)
|
val span = MatrixPermalinkSpan(url, callback)
|
||||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasMatch
|
return hasMatch
|
||||||
*/
|
|
||||||
return false
|
// return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.crypto
|
package org.matrix.android.sdk.api.session.room.crypto
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
|
||||||
interface RoomCryptoService {
|
interface RoomCryptoService {
|
||||||
|
@ -30,6 +29,5 @@ interface RoomCryptoService {
|
||||||
/**
|
/**
|
||||||
* Enable encryption of the room
|
* Enable encryption of the room
|
||||||
*/
|
*/
|
||||||
fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM,
|
suspend fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM)
|
||||||
callback: MatrixCallback<Unit>)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.api.session.room.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing the EventType.STATE_ROOM_SERVER_ACL state event content
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#m-room-server-acl
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class RoomServerAclContent(
|
||||||
|
/**
|
||||||
|
* True to allow server names that are IP address literals. False to deny.
|
||||||
|
* Defaults to true if missing or otherwise not a boolean.
|
||||||
|
* This is strongly recommended to be set to false as servers running with IP literal names are strongly
|
||||||
|
* discouraged in order to require legitimate homeservers to be backed by a valid registered domain name.
|
||||||
|
*/
|
||||||
|
@Json(name = "allow_ip_literals")
|
||||||
|
val allowIpLiterals: Boolean = true,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server names to allow in the room, excluding any port information. Wildcards may be used to cover
|
||||||
|
* a wider range of hosts, where * matches zero or more characters and ? matches exactly one character.
|
||||||
|
*
|
||||||
|
* This defaults to an empty list when not provided, effectively disallowing every server.
|
||||||
|
*/
|
||||||
|
@Json(name = "allow")
|
||||||
|
val allowList: List<String> = emptyList(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The server names to disallow in the room, excluding any port information. Wildcards may be used to cover
|
||||||
|
* a wider range of hosts, where * matches zero or more characters and ? matches exactly one character.
|
||||||
|
*
|
||||||
|
* This defaults to an empty list when not provided.
|
||||||
|
*/
|
||||||
|
@Json(name = "deny")
|
||||||
|
val denyList: List<String> = emptyList()
|
||||||
|
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val ALL = "*"
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,10 @@
|
||||||
package org.matrix.android.sdk.api.session.room.notification
|
package org.matrix.android.sdk.api.session.room.notification
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
interface RoomPushRuleService {
|
interface RoomPushRuleService {
|
||||||
|
|
||||||
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
|
fun getLiveRoomNotificationState(): LiveData<RoomNotificationState>
|
||||||
|
|
||||||
fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable
|
suspend fun setRoomNotificationState(roomNotificationState: RoomNotificationState)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.reporting
|
package org.matrix.android.sdk.api.session.room.reporting
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to report content of an event.
|
* This interface defines methods to report content of an event.
|
||||||
*/
|
*/
|
||||||
|
@ -28,5 +25,5 @@ interface ReportingService {
|
||||||
* Report content
|
* Report content
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid
|
||||||
*/
|
*/
|
||||||
fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun reportContent(eventId: String, score: Int, reason: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
package org.matrix.android.sdk.api.session.room.send
|
package org.matrix.android.sdk.api.session.room.send
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
interface DraftService {
|
interface DraftService {
|
||||||
|
@ -26,12 +24,12 @@ interface DraftService {
|
||||||
/**
|
/**
|
||||||
* Save or update a draft to the room
|
* Save or update a draft to the room
|
||||||
*/
|
*/
|
||||||
fun saveDraft(draft: UserDraft, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun saveDraft(draft: UserDraft)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the last draft, basically just after sending the message
|
* Delete the last draft, basically just after sending the message
|
||||||
*/
|
*/
|
||||||
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
|
suspend fun deleteDraft()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current draft or null
|
* Return the current draft or null
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.tags
|
package org.matrix.android.sdk.api.session.room.tags
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to handle tags of a room. It's implemented at the room level.
|
* This interface defines methods to handle tags of a room. It's implemented at the room level.
|
||||||
*/
|
*/
|
||||||
|
@ -26,10 +23,10 @@ interface TagsService {
|
||||||
/**
|
/**
|
||||||
* Add a tag to a room
|
* Add a tag to a room
|
||||||
*/
|
*/
|
||||||
fun addTag(tag: String, order: Double?, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun addTag(tag: String, order: Double?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove tag from a room
|
* Remove tag from a room
|
||||||
*/
|
*/
|
||||||
fun deleteTag(tag: String, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun deleteTag(tag: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.search
|
package org.matrix.android.sdk.api.session.search
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to search messages in rooms.
|
* This interface defines methods to search messages in rooms.
|
||||||
*/
|
*/
|
||||||
|
@ -35,15 +32,13 @@ interface SearchService {
|
||||||
* @param beforeLimit how many events before the result are returned.
|
* @param beforeLimit how many events before the result are returned.
|
||||||
* @param afterLimit how many events after the result are returned.
|
* @param afterLimit how many events after the result are returned.
|
||||||
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
|
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
|
||||||
* @param callback Callback to get the search result
|
|
||||||
*/
|
*/
|
||||||
fun search(searchTerm: String,
|
suspend fun search(searchTerm: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
nextBatch: String?,
|
nextBatch: String?,
|
||||||
orderByRecent: Boolean,
|
orderByRecent: Boolean,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
beforeLimit: Int,
|
beforeLimit: Int,
|
||||||
afterLimit: Int,
|
afterLimit: Int,
|
||||||
includeProfile: Boolean,
|
includeProfile: Boolean): SearchResult
|
||||||
callback: MatrixCallback<SearchResult>): Cancelable
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,9 +241,9 @@ internal class UpdateTrustWorker(context: Context,
|
||||||
private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel {
|
private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel {
|
||||||
Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds")
|
Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds")
|
||||||
// The set of “all users” depends on the type of room:
|
// The set of “all users” depends on the type of room:
|
||||||
// For regular / topic rooms, all users including yourself, are considered when decorating a room
|
// For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room
|
||||||
// For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room
|
// For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room
|
||||||
val listToCheck = if (roomSummaryEntity.isDirect) {
|
val listToCheck = if (roomSummaryEntity.isDirect || activeMemberUserIds.size <= 2) {
|
||||||
activeMemberUserIds.filter { it != myUserId }
|
activeMemberUserIds.filter { it != myUserId }
|
||||||
} else {
|
} else {
|
||||||
activeMemberUserIds
|
activeMemberUserIds
|
||||||
|
|
|
@ -52,5 +52,8 @@ internal class TimeOutInterceptor @Inject constructor() : Interceptor {
|
||||||
const val CONNECT_TIMEOUT = "CONNECT_TIMEOUT"
|
const val CONNECT_TIMEOUT = "CONNECT_TIMEOUT"
|
||||||
const val READ_TIMEOUT = "READ_TIMEOUT"
|
const val READ_TIMEOUT = "READ_TIMEOUT"
|
||||||
const val WRITE_TIMEOUT = "WRITE_TIMEOUT"
|
const val WRITE_TIMEOUT = "WRITE_TIMEOUT"
|
||||||
|
|
||||||
|
// 1 minute
|
||||||
|
const val DEFAULT_LONG_TIMEOUT: Long = 60_000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,45 +16,28 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.raw
|
package org.matrix.android.sdk.internal.raw
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.raw.RawCacheStrategy
|
import org.matrix.android.sdk.api.raw.RawCacheStrategy
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRawService @Inject constructor(
|
internal class DefaultRawService @Inject constructor(
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val getUrlTask: GetUrlTask,
|
private val getUrlTask: GetUrlTask,
|
||||||
private val cleanRawCacheTask: CleanRawCacheTask
|
private val cleanRawCacheTask: CleanRawCacheTask
|
||||||
) : RawService {
|
) : RawService {
|
||||||
override fun getUrl(url: String,
|
override suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String {
|
||||||
rawCacheStrategy: RawCacheStrategy,
|
return getUrlTask.execute(GetUrlTask.Params(url, rawCacheStrategy))
|
||||||
matrixCallback: MatrixCallback<String>): Cancelable {
|
|
||||||
return getUrlTask
|
|
||||||
.configureWith(GetUrlTask.Params(url, rawCacheStrategy)) {
|
|
||||||
callback = matrixCallback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getWellknown(userId: String,
|
override suspend fun getWellknown(userId: String): String {
|
||||||
matrixCallback: MatrixCallback<String>): Cancelable {
|
|
||||||
val homeServerDomain = userId.substringAfter(":")
|
val homeServerDomain = userId.substringAfter(":")
|
||||||
return getUrl(
|
return getUrl(
|
||||||
"https://$homeServerDomain/.well-known/matrix/client",
|
"https://$homeServerDomain/.well-known/matrix/client",
|
||||||
RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false),
|
RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
|
||||||
matrixCallback
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun clearCache() {
|
||||||
return cleanRawCacheTask
|
cleanRawCacheTask.execute(Unit)
|
||||||
.configureWith(Unit) {
|
|
||||||
callback = matrixCallback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,20 +16,13 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.group
|
package org.matrix.android.sdk.internal.session.group
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.group.Group
|
import org.matrix.android.sdk.api.session.group.Group
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
|
|
||||||
internal class DefaultGroup(override val groupId: String,
|
internal class DefaultGroup(override val groupId: String,
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val getGroupDataTask: GetGroupDataTask) : Group {
|
private val getGroupDataTask: GetGroupDataTask) : Group {
|
||||||
|
|
||||||
override fun fetchGroupData(callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun fetchGroupData() {
|
||||||
val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId))
|
val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId))
|
||||||
return getGroupDataTask.configureWith(params) {
|
getGroupDataTask.execute(params)
|
||||||
this.callback = callback
|
|
||||||
}.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.group
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.session.group.Group
|
import org.matrix.android.sdk.api.session.group.Group
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GroupFactory {
|
internal interface GroupFactory {
|
||||||
|
@ -26,14 +25,12 @@ internal interface GroupFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask,
|
internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask) :
|
||||||
private val taskExecutor: TaskExecutor) :
|
|
||||||
GroupFactory {
|
GroupFactory {
|
||||||
|
|
||||||
override fun create(groupId: String): Group {
|
override fun create(groupId: String): Group {
|
||||||
return DefaultGroup(
|
return DefaultGroup(
|
||||||
groupId = groupId,
|
groupId = groupId,
|
||||||
taskExecutor = taskExecutor,
|
|
||||||
getGroupDataTask = getGroupDataTask
|
getGroupDataTask = getGroupDataTask
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.internal.util.ensureProtocol
|
import org.matrix.android.sdk.internal.util.ensureProtocol
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
@ -243,7 +244,20 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUserConsent(): Boolean {
|
||||||
|
return identityStore.getIdentityData()?.userConsent.orFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserConsent(newValue: Boolean) {
|
||||||
|
identityStore.setUserConsent(newValue)
|
||||||
|
}
|
||||||
|
|
||||||
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
|
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
|
||||||
|
if (!getUserConsent()) {
|
||||||
|
callback.onFailure(IdentityServiceError.UserConsentNotProvided)
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
|
||||||
if (threePids.isEmpty()) {
|
if (threePids.isEmpty()) {
|
||||||
callback.onSuccess(emptyList())
|
callback.onSuccess(emptyList())
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
|
@ -255,6 +269,9 @@ internal class DefaultIdentityService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable {
|
override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable {
|
||||||
|
// Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent
|
||||||
|
// to the home server, and not emails and phone numbers from the contact book of the user
|
||||||
|
|
||||||
if (threePids.isEmpty()) {
|
if (threePids.isEmpty()) {
|
||||||
callback.onSuccess(emptyMap())
|
callback.onSuccess(emptyMap())
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.identity.db.IdentityRealmModule
|
||||||
import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore
|
import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStoreMigration
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
@ -59,6 +60,7 @@ internal abstract class IdentityModule {
|
||||||
@SessionScope
|
@SessionScope
|
||||||
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||||
@SessionFilesDirectory directory: File,
|
@SessionFilesDirectory directory: File,
|
||||||
|
migration: RealmIdentityStoreMigration,
|
||||||
@UserMd5 userMd5: String): RealmConfiguration {
|
@UserMd5 userMd5: String): RealmConfiguration {
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
|
@ -66,6 +68,8 @@ internal abstract class IdentityModule {
|
||||||
.apply {
|
.apply {
|
||||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||||
}
|
}
|
||||||
|
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
|
||||||
|
.migration(migration)
|
||||||
.allowWritesOnUiThread(true)
|
.allowWritesOnUiThread(true)
|
||||||
.modules(IdentityRealmModule())
|
.modules(IdentityRealmModule())
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -20,5 +20,6 @@ internal data class IdentityData(
|
||||||
val identityServerUrl: String?,
|
val identityServerUrl: String?,
|
||||||
val token: String?,
|
val token: String?,
|
||||||
val hashLookupPepper: String?,
|
val hashLookupPepper: String?,
|
||||||
val hashLookupAlgorithm: List<String>
|
val hashLookupAlgorithm: List<String>,
|
||||||
|
val userConsent: Boolean
|
||||||
)
|
)
|
||||||
|
|
|
@ -27,6 +27,8 @@ internal interface IdentityStore {
|
||||||
|
|
||||||
fun setToken(token: String?)
|
fun setToken(token: String?)
|
||||||
|
|
||||||
|
fun setUserConsent(consent: Boolean)
|
||||||
|
|
||||||
fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
|
fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,7 +23,8 @@ internal open class IdentityDataEntity(
|
||||||
var identityServerUrl: String? = null,
|
var identityServerUrl: String? = null,
|
||||||
var token: String? = null,
|
var token: String? = null,
|
||||||
var hashLookupPepper: String? = null,
|
var hashLookupPepper: String? = null,
|
||||||
var hashLookupAlgorithm: RealmList<String> = RealmList()
|
var hashLookupAlgorithm: RealmList<String> = RealmList(),
|
||||||
|
var userConsent: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
|
@ -52,6 +52,13 @@ internal fun IdentityDataEntity.Companion.setToken(realm: Realm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun IdentityDataEntity.Companion.setUserConsent(realm: Realm,
|
||||||
|
newConsent: Boolean) {
|
||||||
|
get(realm)?.apply {
|
||||||
|
userConsent = newConsent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm,
|
internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm,
|
||||||
pepper: String,
|
pepper: String,
|
||||||
algorithms: List<String>) {
|
algorithms: List<String>) {
|
||||||
|
|
|
@ -26,7 +26,8 @@ internal object IdentityMapper {
|
||||||
identityServerUrl = entity.identityServerUrl,
|
identityServerUrl = entity.identityServerUrl,
|
||||||
token = entity.token,
|
token = entity.token,
|
||||||
hashLookupPepper = entity.hashLookupPepper,
|
hashLookupPepper = entity.hashLookupPepper,
|
||||||
hashLookupAlgorithm = entity.hashLookupAlgorithm.toList()
|
hashLookupAlgorithm = entity.hashLookupAlgorithm.toList(),
|
||||||
|
userConsent = entity.userConsent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,14 @@ internal class RealmIdentityStore @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setUserConsent(consent: Boolean) {
|
||||||
|
Realm.getInstance(realmConfiguration).use {
|
||||||
|
it.executeTransaction { realm ->
|
||||||
|
IdentityDataEntity.setUserConsent(realm, consent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) {
|
override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) {
|
||||||
Realm.getInstance(realmConfiguration).use {
|
Realm.getInstance(realmConfiguration).use {
|
||||||
it.executeTransaction { realm ->
|
it.executeTransaction { realm ->
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.identity.db
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import io.realm.RealmMigration
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val IDENTITY_STORE_SCHEMA_VERSION = 1L
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
|
||||||
|
|
||||||
|
if (oldVersion <= 0) migrateTo1(realm)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 0 -> 1")
|
||||||
|
Timber.d("Add field userConsent (Boolean) and set the value to false")
|
||||||
|
|
||||||
|
realm.schema.get("IdentityDataEntity")
|
||||||
|
?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,6 @@
|
||||||
package org.matrix.android.sdk.internal.session.notification
|
package org.matrix.android.sdk.internal.session.notification
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleKind
|
import org.matrix.android.sdk.api.pushrules.RuleKind
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleSetKey
|
import org.matrix.android.sdk.api.pushrules.RuleSetKey
|
||||||
|
@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.pushrules.getActions
|
||||||
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
||||||
import org.matrix.android.sdk.api.pushrules.rest.RuleSet
|
import org.matrix.android.sdk.api.pushrules.rest.RuleSet
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
|
import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
|
||||||
import org.matrix.android.sdk.internal.database.model.PushRulesEntity
|
import org.matrix.android.sdk.internal.database.model.PushRulesEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
@ -103,37 +101,21 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean) {
|
||||||
// The rules will be updated, and will come back from the next sync response
|
// The rules will be updated, and will come back from the next sync response
|
||||||
return updatePushRuleEnableStatusTask
|
updatePushRuleEnableStatusTask.execute(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled))
|
||||||
.configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) {
|
||||||
return addPushRuleTask
|
addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule))
|
||||||
.configureWith(AddPushRuleTask.Params(kind, pushRule)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) {
|
||||||
return updatePushRuleActionsTask
|
updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule))
|
||||||
.configureWith(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) {
|
||||||
return removePushRuleTask
|
removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule))
|
||||||
.configureWith(RemovePushRuleTask.Params(kind, pushRule)) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
|
override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) {
|
||||||
|
|
|
@ -101,13 +101,13 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
return cryptoService.shouldEncryptForInvitedMembers(roomId)
|
return cryptoService.shouldEncryptForInvitedMembers(roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enableEncryption(algorithm: String, callback: MatrixCallback<Unit>) {
|
override suspend fun enableEncryption(algorithm: String) {
|
||||||
when {
|
when {
|
||||||
isEncrypted() -> {
|
isEncrypted() -> {
|
||||||
callback.onFailure(IllegalStateException("Encryption is already enabled for this room"))
|
throw IllegalStateException("Encryption is already enabled for this room")
|
||||||
}
|
}
|
||||||
algorithm != MXCRYPTO_ALGORITHM_MEGOLM -> {
|
algorithm != MXCRYPTO_ALGORITHM_MEGOLM -> {
|
||||||
callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported"))
|
throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val params = SendStateTask.Params(
|
val params = SendStateTask.Params(
|
||||||
|
@ -118,11 +118,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
"algorithm" to algorithm
|
"algorithm" to algorithm
|
||||||
))
|
))
|
||||||
|
|
||||||
sendStateTask
|
sendStateTask.execute(params)
|
||||||
.configureWith(params) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
|
internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
|
||||||
|
@ -46,12 +48,15 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
|
||||||
val roomMembers = RoomMemberHelper(realm, roomId)
|
val roomMembers = RoomMemberHelper(realm, roomId)
|
||||||
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
|
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
|
||||||
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
|
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
|
||||||
|
val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false
|
||||||
|
if (isDirectRoom) {
|
||||||
if (members.size == 1) {
|
if (members.size == 1) {
|
||||||
res = members.firstOrNull()?.avatarUrl
|
res = members.firstOrNull()?.avatarUrl
|
||||||
} else if (members.size == 2) {
|
} else if (members.size == 2) {
|
||||||
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
|
val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst()
|
||||||
res = firstOtherMember?.avatarUrl
|
res = firstOtherMember?.avatarUrl
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.alias
|
package org.matrix.android.sdk.internal.session.room.alias
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.findByAlias
|
import org.matrix.android.sdk.internal.database.query.findByAlias
|
||||||
|
@ -24,8 +27,6 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import io.realm.Realm
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
|
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
|
||||||
|
@ -50,9 +51,11 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
|
||||||
} else if (!params.searchOnServer) {
|
} else if (!params.searchOnServer) {
|
||||||
Optional.from<String>(null)
|
Optional.from<String>(null)
|
||||||
} else {
|
} else {
|
||||||
roomId = executeRequest<RoomAliasDescription>(eventBus) {
|
roomId = tryOrNull("## Failed to get roomId from alias") {
|
||||||
|
executeRequest<RoomAliasDescription>(eventBus) {
|
||||||
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
|
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
|
||||||
}.roomId
|
}
|
||||||
|
}?.roomId
|
||||||
Optional.from(roomId)
|
Optional.from(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,18 +19,14 @@ package org.matrix.android.sdk.internal.session.room.draft
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.session.room.send.DraftService
|
import org.matrix.android.sdk.api.session.room.send.DraftService
|
||||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
|
|
||||||
internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val draftRepository: DraftRepository,
|
private val draftRepository: DraftRepository,
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
private val coroutineDispatchers: MatrixCoroutineDispatchers
|
||||||
) : DraftService {
|
) : DraftService {
|
||||||
|
|
||||||
|
@ -43,14 +39,14 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private
|
||||||
* The draft stack can contain several drafts. Depending of the draft to save, it will update the top draft, or create a new draft,
|
* The draft stack can contain several drafts. Depending of the draft to save, it will update the top draft, or create a new draft,
|
||||||
* or even move an existing draft to the top of the list
|
* or even move an existing draft to the top of the list
|
||||||
*/
|
*/
|
||||||
override fun saveDraft(draft: UserDraft, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun saveDraft(draft: UserDraft) {
|
||||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
withContext(coroutineDispatchers.main) {
|
||||||
draftRepository.saveDraft(roomId, draft)
|
draftRepository.saveDraft(roomId, draft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun deleteDraft() {
|
||||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
|
withContext(coroutineDispatchers.main) {
|
||||||
draftRepository.deleteDraft(roomId)
|
draftRepository.deleteDraft(roomId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,8 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
} else if (roomEntity?.membership == Membership.JOIN) {
|
} else if (roomEntity?.membership == Membership.JOIN) {
|
||||||
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||||
|
val invitedCount = roomSummary?.invitedMembersCount ?: 0
|
||||||
|
val joinedCount = roomSummary?.joinedMembersCount ?: 0
|
||||||
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
|
||||||
roomSummary.heroes.mapNotNull { userId ->
|
roomSummary.heroes.mapNotNull { userId ->
|
||||||
roomMembers.getLastRoomMember(userId)?.takeIf {
|
roomMembers.getLastRoomMember(userId)?.takeIf {
|
||||||
|
@ -102,22 +104,49 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
activeMembers.where()
|
activeMembers.where()
|
||||||
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||||
.limit(3)
|
.limit(5)
|
||||||
.findAll()
|
.findAll()
|
||||||
.createSnapshot()
|
.createSnapshot()
|
||||||
}
|
}
|
||||||
val otherMembersCount = otherMembersSubset.count()
|
val otherMembersCount = otherMembersSubset.count()
|
||||||
name = when (otherMembersCount) {
|
name = when (otherMembersCount) {
|
||||||
0 -> stringProvider.getString(R.string.room_displayname_empty_room)
|
0 -> {
|
||||||
|
stringProvider.getString(R.string.room_displayname_empty_room)
|
||||||
|
// TODO (was xx and yyy) ...
|
||||||
|
}
|
||||||
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers)
|
||||||
2 -> stringProvider.getString(R.string.room_displayname_two_members,
|
2 -> {
|
||||||
|
stringProvider.getString(R.string.room_displayname_two_members,
|
||||||
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers)
|
||||||
)
|
)
|
||||||
else -> stringProvider.getQuantityString(R.plurals.room_displayname_three_and_more_members,
|
}
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1,
|
3 -> {
|
||||||
|
stringProvider.getString(R.string.room_displayname_3_members,
|
||||||
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
roomMembers.getNumberOfJoinedMembers() - 1)
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[2], roomMembers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
4 -> {
|
||||||
|
stringProvider.getString(R.string.room_displayname_4_members,
|
||||||
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[2], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[3], roomMembers)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val remainingCount = invitedCount + joinedCount - otherMembersCount + 1
|
||||||
|
stringProvider.getQuantityString(
|
||||||
|
R.plurals.room_displayname_four_and_more_members,
|
||||||
|
remainingCount,
|
||||||
|
resolveRoomMemberName(otherMembersSubset[0], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[1], roomMembers),
|
||||||
|
resolveRoomMemberName(otherMembersSubset[2], roomMembers),
|
||||||
|
remainingCount
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return name ?: roomId
|
return name ?: roomId
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.membership
|
package org.matrix.android.sdk.internal.session.room.membership
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.RealmQuery
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||||
|
@ -25,8 +27,6 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import io.realm.Realm
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is an helper around STATE_ROOM_MEMBER events.
|
* This class is an helper around STATE_ROOM_MEMBER events.
|
||||||
|
|
|
@ -21,21 +21,16 @@ import androidx.lifecycle.Transformations
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleScope
|
import org.matrix.android.sdk.api.pushrules.RuleScope
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.database.model.PushRuleEntity
|
import org.matrix.android.sdk.internal.database.model.PushRuleEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
|
|
||||||
internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
|
private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy)
|
||||||
private val taskExecutor: TaskExecutor)
|
|
||||||
: RoomPushRuleService {
|
: RoomPushRuleService {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
|
@ -49,12 +44,8 @@ internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun setRoomNotificationState(roomNotificationState: RoomNotificationState) {
|
||||||
return setRoomNotificationStateTask
|
setRoomNotificationStateTask.execute(SetRoomNotificationStateTask.Params(roomId, roomNotificationState))
|
||||||
.configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) {
|
|
||||||
this.callback = matrixCallback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPushRuleForRoom(): LiveData<RoomPushRule?> {
|
private fun getPushRuleForRoom(): LiveData<RoomPushRule?> {
|
||||||
|
|
|
@ -18,14 +18,9 @@ package org.matrix.android.sdk.internal.session.room.reporting
|
||||||
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.room.reporting.ReportingService
|
import org.matrix.android.sdk.api.session.room.reporting.ReportingService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
|
|
||||||
internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String,
|
internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val reportContentTask: ReportContentTask
|
private val reportContentTask: ReportContentTask
|
||||||
) : ReportingService {
|
) : ReportingService {
|
||||||
|
|
||||||
|
@ -34,13 +29,8 @@ internal class DefaultReportingService @AssistedInject constructor(@Assisted pri
|
||||||
fun create(roomId: String): ReportingService
|
fun create(roomId: String): ReportingService
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun reportContent(eventId: String, score: Int, reason: String) {
|
||||||
val params = ReportContentTask.Params(roomId, eventId, score, reason)
|
val params = ReportContentTask.Params(roomId, eventId, score, reason)
|
||||||
|
reportContentTask.execute(params)
|
||||||
return reportContentTask
|
|
||||||
.configureWith(params) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,15 +18,10 @@ package org.matrix.android.sdk.internal.session.room.tags
|
||||||
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.room.tags.TagsService
|
import org.matrix.android.sdk.api.session.room.tags.TagsService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
|
|
||||||
internal class DefaultTagsService @AssistedInject constructor(
|
internal class DefaultTagsService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val addTagToRoomTask: AddTagToRoomTask,
|
private val addTagToRoomTask: AddTagToRoomTask,
|
||||||
private val deleteTagFromRoomTask: DeleteTagFromRoomTask
|
private val deleteTagFromRoomTask: DeleteTagFromRoomTask
|
||||||
) : TagsService {
|
) : TagsService {
|
||||||
|
@ -36,21 +31,13 @@ internal class DefaultTagsService @AssistedInject constructor(
|
||||||
fun create(roomId: String): TagsService
|
fun create(roomId: String): TagsService
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addTag(tag: String, order: Double?, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun addTag(tag: String, order: Double?) {
|
||||||
val params = AddTagToRoomTask.Params(roomId, tag, order)
|
val params = AddTagToRoomTask.Params(roomId, tag, order)
|
||||||
return addTagToRoomTask
|
addTagToRoomTask.execute(params)
|
||||||
.configureWith(params) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteTag(tag: String, callback: MatrixCallback<Unit>): Cancelable {
|
override suspend fun deleteTag(tag: String) {
|
||||||
val params = DeleteTagFromRoomTask.Params(roomId, tag)
|
val params = DeleteTagFromRoomTask.Params(roomId, tag)
|
||||||
return deleteTagFromRoomTask
|
deleteTagFromRoomTask.execute(params)
|
||||||
.configureWith(params) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,30 +16,23 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.search
|
package org.matrix.android.sdk.internal.session.search
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.search.SearchResult
|
import org.matrix.android.sdk.api.session.search.SearchResult
|
||||||
import org.matrix.android.sdk.api.session.search.SearchService
|
import org.matrix.android.sdk.api.session.search.SearchService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
|
|
||||||
internal class DefaultSearchService @Inject constructor(
|
internal class DefaultSearchService @Inject constructor(
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val searchTask: SearchTask
|
private val searchTask: SearchTask
|
||||||
) : SearchService {
|
) : SearchService {
|
||||||
|
|
||||||
override fun search(searchTerm: String,
|
override suspend fun search(searchTerm: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
nextBatch: String?,
|
nextBatch: String?,
|
||||||
orderByRecent: Boolean,
|
orderByRecent: Boolean,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
beforeLimit: Int,
|
beforeLimit: Int,
|
||||||
afterLimit: Int,
|
afterLimit: Int,
|
||||||
includeProfile: Boolean,
|
includeProfile: Boolean): SearchResult {
|
||||||
callback: MatrixCallback<SearchResult>): Cancelable {
|
return searchTask.execute(SearchTask.Params(
|
||||||
return searchTask
|
|
||||||
.configureWith(SearchTask.Params(
|
|
||||||
searchTerm = searchTerm,
|
searchTerm = searchTerm,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
nextBatch = nextBatch,
|
nextBatch = nextBatch,
|
||||||
|
@ -48,8 +41,6 @@ internal class DefaultSearchService @Inject constructor(
|
||||||
beforeLimit = beforeLimit,
|
beforeLimit = beforeLimit,
|
||||||
afterLimit = afterLimit,
|
afterLimit = afterLimit,
|
||||||
includeProfile = includeProfile
|
includeProfile = includeProfile
|
||||||
)) {
|
))
|
||||||
this.callback = callback
|
|
||||||
}.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,18 +17,21 @@
|
||||||
package org.matrix.android.sdk.internal.session.sync
|
package org.matrix.android.sdk.internal.session.sync
|
||||||
|
|
||||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||||
|
import org.matrix.android.sdk.internal.network.TimeOutInterceptor
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.SyncResponse
|
import org.matrix.android.sdk.internal.session.sync.model.SyncResponse
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Headers
|
import retrofit2.http.Header
|
||||||
import retrofit2.http.QueryMap
|
import retrofit2.http.QueryMap
|
||||||
|
|
||||||
internal interface SyncAPI {
|
internal interface SyncAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set all the timeouts to 1 minute
|
* Set all the timeouts to 1 minute by default
|
||||||
*/
|
*/
|
||||||
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync")
|
||||||
fun sync(@QueryMap params: Map<String, String>): Call<SyncResponse>
|
fun sync(@QueryMap params: Map<String, String>,
|
||||||
|
@Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT,
|
||||||
|
@Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT,
|
||||||
|
@Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT
|
||||||
|
): Call<SyncResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.R
|
import org.matrix.android.sdk.R
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.network.TimeOutInterceptor
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService
|
import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService
|
||||||
import org.matrix.android.sdk.internal.session.filter.FilterRepository
|
import org.matrix.android.sdk.internal.session.filter.FilterRepository
|
||||||
|
@ -78,8 +79,13 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
// Maybe refresh the home server capabilities data we know
|
// Maybe refresh the home server capabilities data we know
|
||||||
getHomeServerCapabilitiesTask.execute(Unit)
|
getHomeServerCapabilitiesTask.execute(Unit)
|
||||||
|
|
||||||
|
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
|
||||||
|
|
||||||
val syncResponse = executeRequest<SyncResponse>(eventBus) {
|
val syncResponse = executeRequest<SyncResponse>(eventBus) {
|
||||||
apiCall = syncAPI.sync(requestParams)
|
apiCall = syncAPI.sync(
|
||||||
|
params = requestParams,
|
||||||
|
readTimeOut = readTimeOut
|
||||||
|
)
|
||||||
}
|
}
|
||||||
syncResponseHandler.handleResponse(syncResponse, token)
|
syncResponseHandler.handleResponse(syncResponse, token)
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
|
@ -87,4 +93,8 @@ internal class DefaultSyncTask @Inject constructor(
|
||||||
}
|
}
|
||||||
Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}")
|
Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TIMEOUT_MARGIN: Long = 10_000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,23 @@
|
||||||
<string name="notice_room_update_by_you">You upgraded this room.</string>
|
<string name="notice_room_update_by_you">You upgraded this room.</string>
|
||||||
<string name="notice_direct_room_update">%s upgraded here.</string>
|
<string name="notice_direct_room_update">%s upgraded here.</string>
|
||||||
<string name="notice_direct_room_update_by_you">You upgraded here.</string>
|
<string name="notice_direct_room_update_by_you">You upgraded here.</string>
|
||||||
|
<string name="notice_room_server_acl_set_title">%s set the server ACLs for this room.</string>
|
||||||
|
<string name="notice_room_server_acl_set_title_by_you">You set the server ACLs for this room.</string>
|
||||||
|
<string name="notice_room_server_acl_set_banned">• Server matching %s are banned.</string>
|
||||||
|
<string name="notice_room_server_acl_set_allowed">• Server matching %s are allowed.</string>
|
||||||
|
<string name="notice_room_server_acl_set_ip_literals_allowed">• Server matching IP literals are allowed.</string>
|
||||||
|
<string name="notice_room_server_acl_set_ip_literals_not_allowed">• Server matching IP literals are banned.</string>
|
||||||
|
|
||||||
|
<string name="notice_room_server_acl_updated_title">%s changed the server ACLs for this room.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_title_by_you">You changed the server ACLs for this room.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_banned">• Server matching %s are now banned.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_was_banned">• Server matching %s were removed from the ban list.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_allowed">• Server matching %s are now allowed.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_was_allowed">• Server matching %s were removed from the allowed list.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_ip_literals_allowed">• Server matching IP literals are now allowed.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_ip_literals_not_allowed">• Server matching IP literals are now banned.</string>
|
||||||
|
<string name="notice_room_server_acl_updated_no_change">No change.</string>
|
||||||
|
<string name="notice_room_server_acl_allow_is_empty">🎉 All servers are banned from participating! This room can no longer be used.</string>
|
||||||
|
|
||||||
<string name="notice_requested_voip_conference">%1$s requested a VoIP conference</string>
|
<string name="notice_requested_voip_conference">%1$s requested a VoIP conference</string>
|
||||||
<string name="notice_requested_voip_conference_by_you">You requested a VoIP conference</string>
|
<string name="notice_requested_voip_conference_by_you">You requested a VoIP conference</string>
|
||||||
|
@ -158,13 +175,22 @@
|
||||||
|
|
||||||
<!-- The 2 parameters will be members' name -->
|
<!-- The 2 parameters will be members' name -->
|
||||||
<string name="room_displayname_two_members">%1$s and %2$s</string>
|
<string name="room_displayname_two_members">%1$s and %2$s</string>
|
||||||
|
<!-- The 3 parameters will be members' name -->
|
||||||
|
<string name="room_displayname_3_members">%1$s, %2$s and %3$s</string>
|
||||||
|
<!-- The 4 parameters will be members' name -->
|
||||||
|
<string name="room_displayname_4_members">%1$s, %2$s, %3$s and %4$s</string>
|
||||||
|
<!-- The 3 first parameters will be members' name -->
|
||||||
|
<plurals name="room_displayname_four_and_more_members">
|
||||||
|
<item quantity="one">%1$s, %2$s, %3$s and %4$d other</item>
|
||||||
|
<item quantity="other">%1$s, %2$s, %3$s and %4$d others</item>
|
||||||
|
</plurals>
|
||||||
<plurals name="room_displayname_three_and_more_members">
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
<item quantity="one">%1$s and 1 other</item>
|
<item quantity="one">%1$s and 1 other</item>
|
||||||
<item quantity="other">%1$s and %2$d others</item>
|
<item quantity="other">%1$s and %2$d others</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<string name="room_displayname_empty_room">Empty room</string>
|
<string name="room_displayname_empty_room">Empty room</string>
|
||||||
|
<string name="room_displayname_empty_room_was">Empty room (was %s)</string>
|
||||||
|
|
||||||
<string name="initial_sync_start_importing_account">Initial Sync:\nImporting account…</string>
|
<string name="initial_sync_start_importing_account">Initial Sync:\nImporting account…</string>
|
||||||
<string name="initial_sync_start_importing_account_crypto">Initial Sync:\nImporting crypto</string>
|
<string name="initial_sync_start_importing_account_crypto">Initial Sync:\nImporting crypto</string>
|
||||||
|
|
|
@ -136,6 +136,7 @@ class DefaultErrorFormatter @Inject constructor(
|
||||||
IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported
|
IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported
|
||||||
IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error
|
IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error
|
||||||
IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error
|
IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error
|
||||||
|
IdentityServiceError.UserConsentNotProvided -> R.string.identity_server_user_consent_not_provided
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
sealed class ContactsBookAction : VectorViewModelAction {
|
sealed class ContactsBookAction : VectorViewModelAction {
|
||||||
data class FilterWith(val filter: String) : ContactsBookAction()
|
data class FilterWith(val filter: String) : ContactsBookAction()
|
||||||
data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : ContactsBookAction()
|
data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : ContactsBookAction()
|
||||||
|
object UserConsentGranted : ContactsBookAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,11 +52,10 @@ class ContactsBookController @Inject constructor(
|
||||||
|
|
||||||
override fun buildModels() {
|
override fun buildModels() {
|
||||||
val currentState = state ?: return
|
val currentState = state ?: return
|
||||||
val hasSearch = currentState.searchTerm.isNotEmpty()
|
|
||||||
when (val asyncMappedContacts = currentState.mappedContacts) {
|
when (val asyncMappedContacts = currentState.mappedContacts) {
|
||||||
is Uninitialized -> renderEmptyState(false)
|
is Uninitialized -> renderEmptyState(false)
|
||||||
is Loading -> renderLoading()
|
is Loading -> renderLoading()
|
||||||
is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch, currentState.onlyBoundContacts)
|
is Success -> renderSuccess(currentState)
|
||||||
is Fail -> renderFailure(asyncMappedContacts.error)
|
is Fail -> renderFailure(asyncMappedContacts.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,13 +74,13 @@ class ContactsBookController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderSuccess(mappedContacts: List<MappedContact>,
|
private fun renderSuccess(state: ContactsBookViewState) {
|
||||||
hasSearch: Boolean,
|
val mappedContacts = state.filteredMappedContacts
|
||||||
onlyBoundContacts: Boolean) {
|
|
||||||
if (mappedContacts.isEmpty()) {
|
if (mappedContacts.isEmpty()) {
|
||||||
renderEmptyState(hasSearch)
|
renderEmptyState(state.searchTerm.isNotEmpty())
|
||||||
} else {
|
} else {
|
||||||
renderContacts(mappedContacts, onlyBoundContacts)
|
renderContacts(mappedContacts, state.onlyBoundContacts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.features.contactsbook
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.airbnb.mvrx.activityViewModel
|
import com.airbnb.mvrx.activityViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
@ -57,10 +58,26 @@ class ContactsBookFragment @Inject constructor(
|
||||||
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
setupFilterView()
|
setupFilterView()
|
||||||
|
setupConsentView()
|
||||||
setupOnlyBoundContactsView()
|
setupOnlyBoundContactsView()
|
||||||
setupCloseView()
|
setupCloseView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupConsentView() {
|
||||||
|
phoneBookSearchForMatrixContacts.setOnClickListener {
|
||||||
|
withState(contactsBookViewModel) { state ->
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.identity_server_consent_dialog_title)
|
||||||
|
.setMessage(getString(R.string.identity_server_consent_dialog_content, state.identityServerUrl ?: ""))
|
||||||
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
contactsBookViewModel.handle(ContactsBookAction.UserConsentGranted)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupOnlyBoundContactsView() {
|
private fun setupOnlyBoundContactsView() {
|
||||||
phoneBookOnlyBoundContacts.checkedChanges()
|
phoneBookOnlyBoundContacts.checkedChanges()
|
||||||
.subscribe {
|
.subscribe {
|
||||||
|
@ -98,6 +115,7 @@ class ContactsBookFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(contactsBookViewModel) { state ->
|
override fun invalidate() = withState(contactsBookViewModel) { state ->
|
||||||
|
phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent
|
||||||
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
|
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
|
||||||
contactsBookController.setData(state)
|
contactsBookController.setData(state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,11 +38,10 @@ import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
import org.matrix.android.sdk.api.session.identity.FoundThreePid
|
||||||
|
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private typealias PhoneBookSearch = String
|
|
||||||
|
|
||||||
class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: ContactsBookViewState,
|
initialState: ContactsBookViewState,
|
||||||
private val contactsDataSource: ContactsDataSource,
|
private val contactsDataSource: ContactsDataSource,
|
||||||
|
@ -85,7 +84,9 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
||||||
private fun loadContacts() {
|
private fun loadContacts() {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
mappedContacts = Loading()
|
mappedContacts = Loading(),
|
||||||
|
identityServerUrl = session.identityService().getCurrentIdentityServerUrl(),
|
||||||
|
userConsent = session.identityService().getUserConsent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +110,9 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performLookup(data: List<MappedContact>) {
|
private fun performLookup(data: List<MappedContact>) {
|
||||||
|
if (!session.identityService().getUserConsent()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val threePids = data.flatMap { contact ->
|
val threePids = data.flatMap { contact ->
|
||||||
contact.emails.map { ThreePid.Email(it.email) } +
|
contact.emails.map { ThreePid.Email(it.email) } +
|
||||||
|
@ -116,8 +120,14 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
||||||
}
|
}
|
||||||
session.identityService().lookUp(threePids, object : MatrixCallback<List<FoundThreePid>> {
|
session.identityService().lookUp(threePids, object : MatrixCallback<List<FoundThreePid>> {
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// Ignore
|
|
||||||
Timber.w(failure, "Unable to perform the lookup")
|
Timber.w(failure, "Unable to perform the lookup")
|
||||||
|
|
||||||
|
// Should not happen, but just to be sure
|
||||||
|
if (failure is IdentityServiceError.UserConsentNotProvided) {
|
||||||
|
setState {
|
||||||
|
copy(userConsent = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSuccess(data: List<FoundThreePid>) {
|
override fun onSuccess(data: List<FoundThreePid>) {
|
||||||
|
@ -171,9 +181,21 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted
|
||||||
when (action) {
|
when (action) {
|
||||||
is ContactsBookAction.FilterWith -> handleFilterWith(action)
|
is ContactsBookAction.FilterWith -> handleFilterWith(action)
|
||||||
is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
|
is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
|
||||||
|
ContactsBookAction.UserConsentGranted -> handleUserConsentGranted()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleUserConsentGranted() {
|
||||||
|
session.identityService().setUserConsent(true)
|
||||||
|
|
||||||
|
setState {
|
||||||
|
copy(userConsent = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the lookup
|
||||||
|
performLookup(allContacts)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) {
|
private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
@ -26,10 +26,14 @@ data class ContactsBookViewState(
|
||||||
val mappedContacts: Async<List<MappedContact>> = Loading(),
|
val mappedContacts: Async<List<MappedContact>> = Loading(),
|
||||||
// Use to filter contacts by display name
|
// Use to filter contacts by display name
|
||||||
val searchTerm: String = "",
|
val searchTerm: String = "",
|
||||||
// Tru to display only bound contacts with their bound 2pid
|
// True to display only bound contacts with their bound 2pid
|
||||||
val onlyBoundContacts: Boolean = false,
|
val onlyBoundContacts: Boolean = false,
|
||||||
// All contacts, filtered by searchTerm and onlyBoundContacts
|
// All contacts, filtered by searchTerm and onlyBoundContacts
|
||||||
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
val filteredMappedContacts: List<MappedContact> = emptyList(),
|
||||||
// True when the identity service has return some data
|
// True when the identity service has return some data
|
||||||
val isBoundRetrieved: Boolean = false
|
val isBoundRetrieved: Boolean = false,
|
||||||
|
// The current identity server url if any
|
||||||
|
val identityServerUrl: String? = null,
|
||||||
|
// User consent to perform lookup (send emails to the identity server)
|
||||||
|
val userConsent: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
|
@ -25,6 +25,7 @@ sealed class DiscoverySettingsAction : VectorViewModelAction {
|
||||||
|
|
||||||
object DisconnectIdentityServer : DiscoverySettingsAction()
|
object DisconnectIdentityServer : DiscoverySettingsAction()
|
||||||
data class ChangeIdentityServer(val url: String) : DiscoverySettingsAction()
|
data class ChangeIdentityServer(val url: String) : DiscoverySettingsAction()
|
||||||
|
data class UpdateUserConsent(val newConsent: Boolean) : DiscoverySettingsAction()
|
||||||
data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||||
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||||
data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction()
|
data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||||
|
|
|
@ -65,6 +65,7 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
buildIdentityServerSection(data)
|
buildIdentityServerSection(data)
|
||||||
val hasIdentityServer = data.identityServer().isNullOrBlank().not()
|
val hasIdentityServer = data.identityServer().isNullOrBlank().not()
|
||||||
if (hasIdentityServer && !data.termsNotSigned) {
|
if (hasIdentityServer && !data.termsNotSigned) {
|
||||||
|
buildConsentSection(data)
|
||||||
buildEmailsSection(data.emailList)
|
buildEmailsSection(data.emailList)
|
||||||
buildMsisdnSection(data.phoneNumbersList)
|
buildMsisdnSection(data.phoneNumbersList)
|
||||||
}
|
}
|
||||||
|
@ -72,6 +73,38 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildConsentSection(data: DiscoverySettingsState) {
|
||||||
|
settingsSectionTitleItem {
|
||||||
|
id("idConsentTitle")
|
||||||
|
titleResId(R.string.settings_discovery_consent_title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.userConsent) {
|
||||||
|
settingsInfoItem {
|
||||||
|
id("idConsentInfo")
|
||||||
|
helperTextResId(R.string.settings_discovery_consent_notice_on)
|
||||||
|
}
|
||||||
|
settingsButtonItem {
|
||||||
|
id("idConsentButton")
|
||||||
|
colorProvider(colorProvider)
|
||||||
|
buttonTitleId(R.string.settings_discovery_consent_action_revoke)
|
||||||
|
buttonStyle(ButtonStyle.DESTRUCTIVE)
|
||||||
|
buttonClickListener { listener?.onTapUpdateUserConsent(false) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settingsInfoItem {
|
||||||
|
id("idConsentInfo")
|
||||||
|
helperTextResId(R.string.settings_discovery_consent_notice_off)
|
||||||
|
}
|
||||||
|
settingsButtonItem {
|
||||||
|
id("idConsentButton")
|
||||||
|
colorProvider(colorProvider)
|
||||||
|
buttonTitleId(R.string.settings_discovery_consent_action_give_consent)
|
||||||
|
buttonClickListener { listener?.onTapUpdateUserConsent(true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildIdentityServerSection(data: DiscoverySettingsState) {
|
private fun buildIdentityServerSection(data: DiscoverySettingsState) {
|
||||||
val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none)
|
val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none)
|
||||||
|
|
||||||
|
@ -359,6 +392,7 @@ class DiscoverySettingsController @Inject constructor(
|
||||||
fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String)
|
fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String)
|
||||||
fun onTapChangeIdentityServer()
|
fun onTapChangeIdentityServer()
|
||||||
fun onTapDisconnectIdentityServer()
|
fun onTapDisconnectIdentityServer()
|
||||||
|
fun onTapUpdateUserConsent(newValue: Boolean)
|
||||||
fun onTapRetryToRetrieveBindings()
|
fun onTapRetryToRetrieveBindings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,23 @@ class DiscoverySettingsFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTapUpdateUserConsent(newValue: Boolean) {
|
||||||
|
if (newValue) {
|
||||||
|
withState(viewModel) { state ->
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.identity_server_consent_dialog_title)
|
||||||
|
.setMessage(getString(R.string.identity_server_consent_dialog_content, state.identityServer.invoke()))
|
||||||
|
.setPositiveButton(R.string.yes) { _, _ ->
|
||||||
|
viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(true))
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.no, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTapRetryToRetrieveBindings() {
|
override fun onTapRetryToRetrieveBindings() {
|
||||||
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
|
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,5 +25,6 @@ data class DiscoverySettingsState(
|
||||||
val emailList: Async<List<PidInfo>> = Uninitialized,
|
val emailList: Async<List<PidInfo>> = Uninitialized,
|
||||||
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
|
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
|
||||||
// Can be true if terms are updated
|
// Can be true if terms are updated
|
||||||
val termsNotSigned: Boolean = false
|
val termsNotSigned: Boolean = false,
|
||||||
|
val userConsent: Boolean = false
|
||||||
) : MvRxState
|
) : MvRxState
|
||||||
|
|
|
@ -63,7 +63,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
|
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
|
||||||
val currentIS = state.identityServer()
|
val currentIS = state.identityServer()
|
||||||
setState {
|
setState {
|
||||||
copy(identityServer = Success(identityServerUrl))
|
copy(
|
||||||
|
identityServer = Success(identityServerUrl),
|
||||||
|
userConsent = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (currentIS != identityServerUrl) retrieveBinding()
|
if (currentIS != identityServerUrl) retrieveBinding()
|
||||||
}
|
}
|
||||||
|
@ -71,7 +74,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setState {
|
setState {
|
||||||
copy(identityServer = Success(identityService.getCurrentIdentityServerUrl()))
|
copy(
|
||||||
|
identityServer = Success(identityService.getCurrentIdentityServerUrl()),
|
||||||
|
userConsent = identityService.getUserConsent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
startListenToIdentityManager()
|
startListenToIdentityManager()
|
||||||
observeThreePids()
|
observeThreePids()
|
||||||
|
@ -97,6 +103,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
|
DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
|
||||||
DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer()
|
DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer()
|
||||||
is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
|
is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
|
||||||
|
is DiscoverySettingsAction.UpdateUserConsent -> handleUpdateUserConsent(action)
|
||||||
is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
|
is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
|
||||||
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
|
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
|
||||||
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true)
|
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true)
|
||||||
|
@ -105,13 +112,23 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) {
|
||||||
|
identityService.setUserConsent(action.newConsent)
|
||||||
|
setState { copy(userConsent = action.newConsent) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun disconnectIdentityServer() {
|
private fun disconnectIdentityServer() {
|
||||||
setState { copy(identityServer = Loading()) }
|
setState { copy(identityServer = Loading()) }
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
awaitCallback<Unit> { session.identityService().disconnect(it) }
|
awaitCallback<Unit> { session.identityService().disconnect(it) }
|
||||||
setState { copy(identityServer = Success(null)) }
|
setState {
|
||||||
|
copy(
|
||||||
|
identityServer = Success(null),
|
||||||
|
userConsent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
setState { copy(identityServer = Fail(failure)) }
|
setState { copy(identityServer = Fail(failure)) }
|
||||||
}
|
}
|
||||||
|
@ -126,7 +143,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
||||||
val data = awaitCallback<String?> {
|
val data = awaitCallback<String?> {
|
||||||
session.identityService().setNewIdentityServer(action.url, it)
|
session.identityService().setNewIdentityServer(action.url, it)
|
||||||
}
|
}
|
||||||
setState { copy(identityServer = Success(data)) }
|
setState {
|
||||||
|
copy(
|
||||||
|
identityServer = Success(data),
|
||||||
|
userConsent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
retrieveBinding()
|
retrieveBinding()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
setState { copy(identityServer = Fail(failure)) }
|
setState { copy(identityServer = Fail(failure)) }
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.grouplist
|
package im.vector.app.features.grouplist
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
@ -28,7 +29,7 @@ import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.functions.BiFunction
|
import io.reactivex.functions.BiFunction
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
|
import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams
|
||||||
|
@ -95,7 +96,9 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro
|
||||||
private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state ->
|
private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state ->
|
||||||
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
|
if (state.selectedGroup?.groupId != action.groupSummary.groupId) {
|
||||||
// We take care of refreshing group data when selecting to be sure we get all the rooms and users
|
// We take care of refreshing group data when selecting to be sure we get all the rooms and users
|
||||||
session.getGroup(action.groupSummary.groupId)?.fetchGroupData(NoOpMatrixCallback())
|
viewModelScope.launch {
|
||||||
|
session.getGroup(action.groupSummary.groupId)?.fetchGroupData()
|
||||||
|
}
|
||||||
setState { copy(selectedGroup = action.groupSummary) }
|
setState { copy(selectedGroup = action.groupSummary) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail
|
package im.vector.app.features.home.room.detail
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
import im.vector.app.core.platform.VectorViewModelAction
|
import im.vector.app.core.platform.VectorViewModelAction
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -24,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
sealed class RoomDetailAction : VectorViewModelAction {
|
sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction()
|
||||||
|
@ -90,4 +93,9 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
|
|
||||||
data class OpenOrCreateDm(val userId: String) : RoomDetailAction()
|
data class OpenOrCreateDm(val userId: String) : RoomDetailAction()
|
||||||
data class JumpToReadReceipt(val userId: String) : RoomDetailAction()
|
data class JumpToReadReceipt(val userId: String) : RoomDetailAction()
|
||||||
|
object QuickActionInvitePeople : RoomDetailAction()
|
||||||
|
object QuickActionSetAvatar : RoomDetailAction()
|
||||||
|
data class SetAvatarAction(val newAvatarUri: Uri, val newAvatarFileName: String) : RoomDetailAction()
|
||||||
|
object QuickActionSetTopic : RoomDetailAction()
|
||||||
|
data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val transitionView: View?) : RoomDetailAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
import im.vector.app.core.dialogs.ConfirmationDialogBuilder
|
||||||
|
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
|
||||||
import im.vector.app.core.dialogs.withColoredButton
|
import im.vector.app.core.dialogs.withColoredButton
|
||||||
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
import im.vector.app.core.epoxy.LayoutManagerStateRestorer
|
||||||
import im.vector.app.core.extensions.cleanup
|
import im.vector.app.core.extensions.cleanup
|
||||||
|
@ -82,6 +83,7 @@ import im.vector.app.core.extensions.showKeyboard
|
||||||
import im.vector.app.core.extensions.trackItemsVisibilityChange
|
import im.vector.app.core.extensions.trackItemsVisibilityChange
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.glide.GlideRequests
|
import im.vector.app.core.glide.GlideRequests
|
||||||
|
import im.vector.app.core.intent.getFilenameFromUri
|
||||||
import im.vector.app.core.intent.getMimeTypeFromUri
|
import im.vector.app.core.intent.getMimeTypeFromUri
|
||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
@ -150,6 +152,7 @@ import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.features.permalink.NavigationInterceptor
|
import im.vector.app.features.permalink.NavigationInterceptor
|
||||||
import im.vector.app.features.permalink.PermalinkHandler
|
import im.vector.app.features.permalink.PermalinkHandler
|
||||||
import im.vector.app.features.reactions.EmojiReactionPickerActivity
|
import im.vector.app.features.reactions.EmojiReactionPickerActivity
|
||||||
|
import im.vector.app.features.roomprofile.RoomProfileActivity
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.settings.VectorSettingsActivity
|
import im.vector.app.features.settings.VectorSettingsActivity
|
||||||
import im.vector.app.features.share.SharedData
|
import im.vector.app.features.share.SharedData
|
||||||
|
@ -197,6 +200,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -231,7 +235,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
JumpToReadMarkerView.Callback,
|
JumpToReadMarkerView.Callback,
|
||||||
AttachmentTypeSelectorView.Callback,
|
AttachmentTypeSelectorView.Callback,
|
||||||
AttachmentsHelper.Callback,
|
AttachmentsHelper.Callback,
|
||||||
// RoomWidgetsBannerView.Callback,
|
GalleryOrCameraDialogHelper.Listener,
|
||||||
ActiveCallView.Callback {
|
ActiveCallView.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -252,6 +256,8 @@ class RoomDetailFragment @Inject constructor(
|
||||||
private const val ircPattern = " (IRC)"
|
private const val ircPattern = " (IRC)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider)
|
||||||
|
|
||||||
private val roomDetailArgs: RoomDetailArgs by args()
|
private val roomDetailArgs: RoomDetailArgs by args()
|
||||||
private val glideRequests by lazy {
|
private val glideRequests by lazy {
|
||||||
GlideApp.with(this)
|
GlideApp.with(this)
|
||||||
|
@ -369,6 +375,12 @@ class RoomDetailFragment @Inject constructor(
|
||||||
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
||||||
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
||||||
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
|
is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
|
||||||
|
RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId)
|
||||||
|
RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show()
|
||||||
|
RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings()
|
||||||
|
is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item ->
|
||||||
|
navigator.openBigImageViewer(requireActivity(), it.view, item)
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +389,24 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onImageReady(uri: Uri?) {
|
||||||
|
uri ?: return
|
||||||
|
roomDetailViewModel.handle(
|
||||||
|
RoomDetailAction.SetAvatarAction(
|
||||||
|
newAvatarUri = uri,
|
||||||
|
newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleOpenRoomSettings() {
|
||||||
|
navigator.openRoomProfile(
|
||||||
|
requireContext(),
|
||||||
|
roomDetailArgs.roomId,
|
||||||
|
RoomProfileActivity.EXTRA_DIRECT_ACCESS_ROOM_SETTINGS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
|
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
|
||||||
navigator.openRoom(requireContext(), openRoom.roomId, null)
|
navigator.openRoom(requireContext(), openRoom.roomId, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
package im.vector.app.features.home.room.detail
|
package im.vector.app.features.home.room.detail
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import im.vector.app.core.platform.VectorViewEvents
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
import im.vector.app.features.command.Command
|
import im.vector.app.features.command.Command
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -43,6 +45,11 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||||
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
||||||
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
|
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
|
||||||
|
|
||||||
|
object OpenInvitePeople : RoomDetailViewEvents()
|
||||||
|
object OpenSetRoomAvatarDialog : RoomDetailViewEvents()
|
||||||
|
object OpenRoomSettings : RoomDetailViewEvents()
|
||||||
|
data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val view: View?) : RoomDetailViewEvents()
|
||||||
|
|
||||||
object ShowWaitingView : RoomDetailViewEvents()
|
object ShowWaitingView : RoomDetailViewEvents()
|
||||||
object HideWaitingView : RoomDetailViewEvents()
|
object HideWaitingView : RoomDetailViewEvents()
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ import io.reactivex.functions.BiFunction
|
||||||
import io.reactivex.rxkotlin.subscribeBy
|
import io.reactivex.rxkotlin.subscribeBy
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
|
@ -99,6 +100,7 @@ import org.matrix.android.sdk.rx.rx
|
||||||
import org.matrix.android.sdk.rx.unwrap
|
import org.matrix.android.sdk.rx.unwrap
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.Exception
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
@ -164,7 +166,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
getUnreadState()
|
getUnreadState()
|
||||||
observeSyncState()
|
observeSyncState()
|
||||||
observeEventDisplayedActions()
|
observeEventDisplayedActions()
|
||||||
getDraftIfAny()
|
loadDraftIfAny()
|
||||||
observeUnreadState()
|
observeUnreadState()
|
||||||
observeMyRoomMember()
|
observeMyRoomMember()
|
||||||
observeActiveRoomWidgets()
|
observeActiveRoomWidgets()
|
||||||
|
@ -275,9 +277,39 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||||
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
||||||
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
|
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
|
||||||
|
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
|
||||||
|
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
|
||||||
|
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
|
||||||
|
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
|
||||||
|
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
|
||||||
|
_viewEvents.post(
|
||||||
|
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
|
||||||
|
)
|
||||||
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
awaitCallback<Unit> {
|
||||||
|
room.updateAvatar(action.newAvatarUri, action.newAvatarFileName, it)
|
||||||
|
}
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleInvitePeople() {
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.OpenInvitePeople)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleQuickSetAvatar() {
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) {
|
private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) {
|
||||||
val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId)
|
val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId)
|
||||||
if (existingDmRoomId == null) {
|
if (existingDmRoomId == null) {
|
||||||
|
@ -475,28 +507,30 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
* Convert a send mode to a draft and save the draft
|
* Convert a send mode to a draft and save the draft
|
||||||
*/
|
*/
|
||||||
private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
|
private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState {
|
||||||
|
viewModelScope.launch(NonCancellable) {
|
||||||
when {
|
when {
|
||||||
it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
|
it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(action.draft)) }
|
setState { copy(sendMode = it.sendMode.copy(action.draft)) }
|
||||||
room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback())
|
room.saveDraft(UserDraft.REGULAR(action.draft))
|
||||||
}
|
}
|
||||||
it.sendMode is SendMode.REPLY -> {
|
it.sendMode is SendMode.REPLY -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||||
room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft))
|
||||||
}
|
}
|
||||||
it.sendMode is SendMode.QUOTE -> {
|
it.sendMode is SendMode.QUOTE -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||||
room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft))
|
||||||
}
|
}
|
||||||
it.sendMode is SendMode.EDIT -> {
|
it.sendMode is SendMode.EDIT -> {
|
||||||
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
setState { copy(sendMode = it.sendMode.copy(text = action.draft)) }
|
||||||
room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback())
|
room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDraftIfAny() {
|
private fun loadDraftIfAny() {
|
||||||
val currentDraft = room.getDraft() ?: return
|
val currentDraft = room.getDraft()
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
// Create a sendMode from a draft and retrieve the TimelineEvent
|
// Create a sendMode from a draft and retrieve the TimelineEvent
|
||||||
|
@ -517,6 +551,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
SendMode.EDIT(timelineEvent, currentDraft.text)
|
SendMode.EDIT(timelineEvent, currentDraft.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> null
|
||||||
} ?: SendMode.REGULAR("", fromSharing = false)
|
} ?: SendMode.REGULAR("", fromSharing = false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -772,11 +807,13 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
private fun popDraft() = withState {
|
private fun popDraft() = withState {
|
||||||
if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) {
|
if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) {
|
||||||
// If we were sharing, we want to get back our last value from draft
|
// If we were sharing, we want to get back our last value from draft
|
||||||
getDraftIfAny()
|
loadDraftIfAny()
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we clear the composer and remove the draft from db
|
// Otherwise we clear the composer and remove the draft from db
|
||||||
setState { copy(sendMode = SendMode.REGULAR("", false)) }
|
setState { copy(sendMode = SendMode.REGULAR("", false)) }
|
||||||
room.deleteDraft(NoOpMatrixCallback())
|
viewModelScope.launch {
|
||||||
|
room.deleteDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1111,15 +1148,15 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReportContent(action: RoomDetailAction.ReportContent) {
|
private fun handleReportContent(action: RoomDetailAction.ReportContent) {
|
||||||
room.reportContent(action.eventId, -100, action.reason, object : MatrixCallback<Unit> {
|
viewModelScope.launch {
|
||||||
override fun onSuccess(data: Unit) {
|
val event = try {
|
||||||
_viewEvents.post(RoomDetailViewEvents.ActionSuccess(action))
|
room.reportContent(action.eventId, -100, action.reason)
|
||||||
|
RoomDetailViewEvents.ActionSuccess(action)
|
||||||
|
} catch (failure: Exception) {
|
||||||
|
RoomDetailViewEvents.ActionFailure(action, failure)
|
||||||
}
|
}
|
||||||
|
_viewEvents.post(event)
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleIgnoreUser(action: RoomDetailAction.IgnoreUser) {
|
private fun handleIgnoreUser(action: RoomDetailAction.IgnoreUser) {
|
||||||
|
|
|
@ -191,6 +191,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
EventType.STATE_ROOM_ALIASES,
|
EventType.STATE_ROOM_ALIASES,
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
|
EventType.STATE_ROOM_SERVER_ACL,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_CANDIDATES,
|
EventType.CALL_CANDIDATES,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
|
|
|
@ -31,10 +31,14 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEve
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
|
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
||||||
|
@ -187,6 +191,11 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
|
||||||
collapsedEventIds.removeAll(mergedEventIds)
|
collapsedEventIds.removeAll(mergedEventIds)
|
||||||
}
|
}
|
||||||
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
|
val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
|
||||||
|
val powerLevelsHelper = roomSummaryHolder.roomSummary?.roomId
|
||||||
|
?.let { activeSessionHolder.getSafeActiveSession()?.getRoom(it) }
|
||||||
|
?.let { it.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)?.content?.toModel<PowerLevelsContent>() }
|
||||||
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: ""
|
||||||
val attributes = MergedRoomCreationItem.Attributes(
|
val attributes = MergedRoomCreationItem.Attributes(
|
||||||
isCollapsed = isCollapsed,
|
isCollapsed = isCollapsed,
|
||||||
mergeData = mergedData,
|
mergeData = mergedData,
|
||||||
|
@ -198,13 +207,19 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
|
||||||
hasEncryptionEvent = hasEncryption,
|
hasEncryptionEvent = hasEncryption,
|
||||||
isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM,
|
isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM,
|
||||||
readReceiptsCallback = callback,
|
readReceiptsCallback = callback,
|
||||||
currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: ""
|
callback = callback,
|
||||||
|
currentUserId = currentUserId,
|
||||||
|
roomSummary = roomSummaryHolder.roomSummary,
|
||||||
|
canChangeAvatar = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false,
|
||||||
|
canChangeTopic = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false,
|
||||||
|
canChangeName = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false
|
||||||
)
|
)
|
||||||
MergedRoomCreationItem_()
|
MergedRoomCreationItem_()
|
||||||
.id(mergeId)
|
.id(mergeId)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
.highlighted(isCollapsed && highlighted)
|
.highlighted(isCollapsed && highlighted)
|
||||||
.attributes(attributes)
|
.attributes(attributes)
|
||||||
|
.movementMethod(createLinkMovementMethod(callback))
|
||||||
.also {
|
.also {
|
||||||
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
EventType.STATE_ROOM_JOIN_RULES,
|
EventType.STATE_ROOM_JOIN_RULES,
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
|
EventType.STATE_ROOM_SERVER_ACL,
|
||||||
EventType.STATE_ROOM_GUEST_ACCESS,
|
EventType.STATE_ROOM_GUEST_ACCESS,
|
||||||
EventType.STATE_ROOM_WIDGET_LEGACY,
|
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||||
EventType.STATE_ROOM_WIDGET,
|
EventType.STATE_ROOM_WIDGET,
|
||||||
|
|
|
@ -19,6 +19,8 @@ package im.vector.app.features.home.room.detail.timeline.format
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import org.matrix.android.sdk.api.extensions.appendNl
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
@ -35,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomServerAclContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
|
import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
|
||||||
|
@ -48,9 +51,12 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NoticeEventFormatter @Inject constructor(private val activeSessionDataSource: ActiveSessionDataSource,
|
class NoticeEventFormatter @Inject constructor(
|
||||||
|
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
|
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
|
||||||
private val sp: StringProvider) {
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val sp: StringProvider
|
||||||
|
) {
|
||||||
|
|
||||||
private val currentUserId: String?
|
private val currentUserId: String?
|
||||||
get() = activeSessionDataSource.currentValue?.orNull()?.myUserId
|
get() = activeSessionDataSource.currentValue?.orNull()?.myUserId
|
||||||
|
@ -72,6 +78,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY ->
|
EventType.STATE_ROOM_HISTORY_VISIBILITY ->
|
||||||
formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
|
formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
|
||||||
|
EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||||
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
|
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs)
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
||||||
EventType.STATE_ROOM_WIDGET,
|
EventType.STATE_ROOM_WIDGET,
|
||||||
|
@ -383,6 +390,79 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatRoomServerAclEvent(event: Event, senderName: String?): String? {
|
||||||
|
val eventContent = event.getClearContent().toModel<RoomServerAclContent>() ?: return null
|
||||||
|
val prevEventContent = event.resolvedPrevContent()?.toModel<RoomServerAclContent>()
|
||||||
|
|
||||||
|
return buildString {
|
||||||
|
// Title
|
||||||
|
append(if (prevEventContent == null) {
|
||||||
|
if (event.isSentByCurrentUser()) {
|
||||||
|
sp.getString(R.string.notice_room_server_acl_set_title_by_you)
|
||||||
|
} else {
|
||||||
|
sp.getString(R.string.notice_room_server_acl_set_title, senderName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (event.isSentByCurrentUser()) {
|
||||||
|
sp.getString(R.string.notice_room_server_acl_updated_title_by_you)
|
||||||
|
} else {
|
||||||
|
sp.getString(R.string.notice_room_server_acl_updated_title, senderName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (eventContent.allowList.isEmpty()) {
|
||||||
|
// Special case for stuck room
|
||||||
|
appendNl(sp.getString(R.string.notice_room_server_acl_allow_is_empty))
|
||||||
|
} else if (vectorPreferences.developerMode()) {
|
||||||
|
// Details, only in developer mode
|
||||||
|
appendAclDetails(eventContent, prevEventContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendAclDetails(eventContent: RoomServerAclContent, prevEventContent: RoomServerAclContent?) {
|
||||||
|
if (prevEventContent == null) {
|
||||||
|
eventContent.allowList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_allowed, it)) }
|
||||||
|
eventContent.denyList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_banned, it)) }
|
||||||
|
if (eventContent.allowIpLiterals) {
|
||||||
|
appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_allowed))
|
||||||
|
} else {
|
||||||
|
appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_not_allowed))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Display only diff
|
||||||
|
var hasChanged = false
|
||||||
|
// New allowed servers
|
||||||
|
(eventContent.allowList - prevEventContent.allowList)
|
||||||
|
.also { hasChanged = hasChanged || it.isNotEmpty() }
|
||||||
|
.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_allowed, it)) }
|
||||||
|
// Removed allowed servers
|
||||||
|
(prevEventContent.allowList - eventContent.allowList)
|
||||||
|
.also { hasChanged = hasChanged || it.isNotEmpty() }
|
||||||
|
.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_allowed, it)) }
|
||||||
|
// New denied servers
|
||||||
|
(eventContent.denyList - prevEventContent.denyList)
|
||||||
|
.also { hasChanged = hasChanged || it.isNotEmpty() }
|
||||||
|
.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_banned, it)) }
|
||||||
|
// Removed denied servers
|
||||||
|
(prevEventContent.denyList - eventContent.denyList)
|
||||||
|
.also { hasChanged = hasChanged || it.isNotEmpty() }
|
||||||
|
.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_banned, it)) }
|
||||||
|
|
||||||
|
if (prevEventContent.allowIpLiterals != eventContent.allowIpLiterals) {
|
||||||
|
hasChanged = true
|
||||||
|
if (eventContent.allowIpLiterals) {
|
||||||
|
appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_allowed))
|
||||||
|
} else {
|
||||||
|
appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_not_allowed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasChanged) {
|
||||||
|
appendNl(sp.getString(R.string.notice_room_server_acl_updated_no_change))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? {
|
private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? {
|
||||||
val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel()
|
val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel()
|
||||||
val canonicalAlias = eventContent?.canonicalAlias
|
val canonicalAlias = eventContent?.canonicalAlias
|
||||||
|
|
|
@ -33,6 +33,7 @@ object TimelineDisplayableEvents {
|
||||||
EventType.STATE_ROOM_ALIASES,
|
EventType.STATE_ROOM_ALIASES,
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
EventType.STATE_ROOM_CANONICAL_ALIAS,
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||||
|
EventType.STATE_ROOM_SERVER_ACL,
|
||||||
EventType.STATE_ROOM_POWER_LEVELS,
|
EventType.STATE_ROOM_POWER_LEVELS,
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
|
|
|
@ -16,11 +16,14 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.item
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.method.MovementMethod
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
@ -28,8 +31,16 @@ import androidx.core.view.updateLayoutParams
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
|
import im.vector.app.core.utils.DebouncedClickListener
|
||||||
|
import im.vector.app.core.utils.tappableMatchingText
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
||||||
|
import me.gujun.android.span.span
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
|
||||||
abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.Holder>() {
|
abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.Holder>() {
|
||||||
|
@ -37,11 +48,16 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
override lateinit var attributes: Attributes
|
override lateinit var attributes: Attributes
|
||||||
|
|
||||||
|
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||||
|
var movementMethod: MovementMethod? = null
|
||||||
|
|
||||||
override fun getViewType() = STUB_ID
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
super.bind(holder)
|
||||||
|
|
||||||
|
bindCreationSummaryTile(holder)
|
||||||
|
|
||||||
if (attributes.isCollapsed) {
|
if (attributes.isCollapsed) {
|
||||||
// Take the oldest data
|
// Take the oldest data
|
||||||
val data = distinctMergeData.lastOrNull()
|
val data = distinctMergeData.lastOrNull()
|
||||||
|
@ -70,9 +86,20 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
holder.avatarView.visibility = View.GONE
|
holder.avatarView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindEncryptionTile(holder, data)
|
||||||
|
} else {
|
||||||
|
holder.avatarView.visibility = View.INVISIBLE
|
||||||
|
holder.summaryView.visibility = View.GONE
|
||||||
|
holder.encryptionTile.isGone = true
|
||||||
|
}
|
||||||
|
// No read receipt for this item
|
||||||
|
holder.readReceiptsView.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindEncryptionTile(holder: Holder, data: Data?) {
|
||||||
if (attributes.hasEncryptionEvent) {
|
if (attributes.hasEncryptionEvent) {
|
||||||
holder.encryptionTile.isVisible = true
|
holder.encryptionTile.isVisible = true
|
||||||
holder.encryptionTile.updateLayoutParams<RelativeLayout.LayoutParams> {
|
holder.encryptionTile.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
this.marginEnd = leftGuideline
|
this.marginEnd = leftGuideline
|
||||||
}
|
}
|
||||||
if (attributes.isEncryptionAlgorithmSecure) {
|
if (attributes.isEncryptionAlgorithmSecure) {
|
||||||
|
@ -98,13 +125,78 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
} else {
|
} else {
|
||||||
holder.encryptionTile.isVisible = false
|
holder.encryptionTile.isVisible = false
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
holder.avatarView.visibility = View.INVISIBLE
|
|
||||||
holder.summaryView.visibility = View.GONE
|
|
||||||
holder.encryptionTile.isGone = true
|
|
||||||
}
|
}
|
||||||
// No read receipt for this item
|
|
||||||
holder.readReceiptsView.isVisible = false
|
private fun bindCreationSummaryTile(holder: Holder) {
|
||||||
|
val roomSummary = attributes.roomSummary
|
||||||
|
val roomDisplayName = roomSummary?.displayName
|
||||||
|
holder.roomNameText.setTextOrHide(roomDisplayName)
|
||||||
|
val isDirect = roomSummary?.isDirect == true
|
||||||
|
val membersCount = roomSummary?.otherMemberIds?.size ?: 0
|
||||||
|
|
||||||
|
if (isDirect) {
|
||||||
|
holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_dm, roomSummary?.displayName ?: "")
|
||||||
|
} else if (roomDisplayName.isNullOrBlank() || roomSummary.name.isBlank()) {
|
||||||
|
holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name)
|
||||||
|
} else {
|
||||||
|
holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val topic = roomSummary?.topic
|
||||||
|
if (topic.isNullOrBlank()) {
|
||||||
|
// do not show hint for DMs or group DMs
|
||||||
|
if (!isDirect) {
|
||||||
|
val addTopicLink = holder.view.resources.getString(R.string.add_a_topic_link_text)
|
||||||
|
val styledText = SpannableString(holder.view.resources.getString(R.string.room_created_summary_no_topic_creation_text, addTopicLink))
|
||||||
|
holder.roomTopicText.setTextOrHide(styledText.tappableMatchingText(addTopicLink, object : ClickableSpan() {
|
||||||
|
override fun onClick(widget: View) {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetTopic)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
holder.roomTopicText.setTextOrHide(
|
||||||
|
span {
|
||||||
|
span(holder.view.resources.getString(R.string.topic_prefix)) {
|
||||||
|
textStyle = "bold"
|
||||||
|
}
|
||||||
|
+topic.linkify(attributes.callback)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
holder.roomTopicText.movementMethod = movementMethod
|
||||||
|
|
||||||
|
val roomItem = roomSummary?.toMatrixItem()
|
||||||
|
val shouldSetAvatar = attributes.canChangeAvatar
|
||||||
|
&& (roomSummary?.isDirect == false || (isDirect && membersCount >= 2))
|
||||||
|
&& roomItem?.avatarUrl.isNullOrBlank()
|
||||||
|
|
||||||
|
holder.roomAvatarImageView.isVisible = roomItem != null
|
||||||
|
if (roomItem != null) {
|
||||||
|
attributes.avatarRenderer.render(roomItem, holder.roomAvatarImageView)
|
||||||
|
holder.roomAvatarImageView.setOnClickListener(DebouncedClickListener({ view ->
|
||||||
|
if (shouldSetAvatar) {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar)
|
||||||
|
} else {
|
||||||
|
// Note: this is no op if there is no avatar on the room
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.ShowRoomAvatarFullScreen(roomItem, view))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.setAvatarButton.isVisible = shouldSetAvatar
|
||||||
|
if (shouldSetAvatar) {
|
||||||
|
holder.setAvatarButton.setOnClickListener(DebouncedClickListener({ _ ->
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.addPeopleButton.isVisible = !isDirect
|
||||||
|
if (!isDirect) {
|
||||||
|
holder.addPeopleButton.setOnClickListener(DebouncedClickListener({ _ ->
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionInvitePeople)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Holder : BasedMergedItem.Holder(STUB_ID) {
|
class Holder : BasedMergedItem.Holder(STUB_ID) {
|
||||||
|
@ -114,6 +206,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
|
|
||||||
val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
|
val e2eTitleTextView by bind<TextView>(R.id.itemVerificationDoneTitleTextView)
|
||||||
val e2eTitleDescriptionView by bind<TextView>(R.id.itemVerificationDoneDetailTextView)
|
val e2eTitleDescriptionView by bind<TextView>(R.id.itemVerificationDoneDetailTextView)
|
||||||
|
|
||||||
|
val roomNameText by bind<TextView>(R.id.roomNameTileText)
|
||||||
|
val roomDescriptionText by bind<TextView>(R.id.roomNameDescriptionText)
|
||||||
|
val roomTopicText by bind<TextView>(R.id.roomNameTopicText)
|
||||||
|
val roomAvatarImageView by bind<ImageView>(R.id.creationTileRoomAvatarImageView)
|
||||||
|
val addPeopleButton by bind<View>(R.id.creationTileAddPeopleButton)
|
||||||
|
val setAvatarButton by bind<View>(R.id.creationTileSetAvatarButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -126,8 +225,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem<MergedRoomCreationItem.H
|
||||||
override val avatarRenderer: AvatarRenderer,
|
override val avatarRenderer: AvatarRenderer,
|
||||||
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
|
||||||
override val onCollapsedStateChanged: (Boolean) -> Unit,
|
override val onCollapsedStateChanged: (Boolean) -> Unit,
|
||||||
|
val callback: TimelineEventController.Callback? = null,
|
||||||
val currentUserId: String,
|
val currentUserId: String,
|
||||||
val hasEncryptionEvent: Boolean,
|
val hasEncryptionEvent: Boolean,
|
||||||
val isEncryptionAlgorithmSecure: Boolean
|
val isEncryptionAlgorithmSecure: Boolean,
|
||||||
|
val roomSummary: RoomSummary?,
|
||||||
|
val canChangeAvatar: Boolean = false,
|
||||||
|
val canChangeName: Boolean = false,
|
||||||
|
val canChangeTopic: Boolean = false
|
||||||
) : BasedMergedItem.Attributes
|
) : BasedMergedItem.Attributes
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.lang.Exception
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
|
@ -169,11 +169,16 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) {
|
private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) {
|
||||||
session.getRoom(action.roomId)?.setRoomNotificationState(action.notificationState, object : MatrixCallback<Unit> {
|
val room = session.getRoom(action.roomId)
|
||||||
override fun onFailure(failure: Throwable) {
|
if (room != null) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
room.setRoomNotificationState(action.notificationState)
|
||||||
|
} catch (failure: Exception) {
|
||||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleToggleTag(action: RoomListAction.ToggleTag) {
|
private fun handleToggleTag(action: RoomListAction.ToggleTag) {
|
||||||
|
@ -185,17 +190,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||||
action.tag.otherTag()
|
action.tag.otherTag()
|
||||||
?.takeIf { room.roomSummary()?.hasTag(it).orFalse() }
|
?.takeIf { room.roomSummary()?.hasTag(it).orFalse() }
|
||||||
?.let { tagToRemove ->
|
?.let { tagToRemove ->
|
||||||
awaitCallback<Unit> { room.deleteTag(tagToRemove, it) }
|
room.deleteTag(tagToRemove)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the tag. We do not handle the order for the moment
|
// Set the tag. We do not handle the order for the moment
|
||||||
awaitCallback<Unit> {
|
room.addTag(action.tag, 0.5)
|
||||||
room.addTag(action.tag, 0.5, it)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
awaitCallback<Unit> {
|
room.deleteTag(action.tag)
|
||||||
room.deleteTag(action.tag, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
_viewEvents.post(RoomListViewEvents.Failure(failure))
|
||||||
|
|
|
@ -69,6 +69,11 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showFailure(throwable: Throwable) {
|
override fun showFailure(throwable: Throwable) {
|
||||||
|
// Only the resumed Fragment can eventually show the error, to avoid multiple dialog display
|
||||||
|
if (!isResumed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
when (throwable) {
|
when (throwable) {
|
||||||
is Failure.Cancelled ->
|
is Failure.Cancelled ->
|
||||||
/* Ignore this error, user has cancelled the action */
|
/* Ignore this error, user has cancelled the action */
|
||||||
|
|
|
@ -207,7 +207,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) {
|
private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) {
|
||||||
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
|
// We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state
|
||||||
currentTask?.cancel()
|
currentTask?.cancel()
|
||||||
currentTask = null
|
|
||||||
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback)
|
currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -248,8 +248,8 @@ class DefaultNavigator @Inject constructor(
|
||||||
context.startActivity(KeysBackupManageActivity.intent(context))
|
context.startActivity(KeysBackupManageActivity.intent(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openRoomProfile(context: Context, roomId: String) {
|
override fun openRoomProfile(context: Context, roomId: String, directAccess: Int?) {
|
||||||
context.startActivity(RoomProfileActivity.newIntent(context, roomId))
|
context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
|
override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) {
|
||||||
|
|
|
@ -78,7 +78,7 @@ interface Navigator {
|
||||||
|
|
||||||
fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)
|
fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false)
|
||||||
|
|
||||||
fun openRoomProfile(context: Context, roomId: String)
|
fun openRoomProfile(context: Context, roomId: String, directAccess: Int? = null)
|
||||||
|
|
||||||
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
|
fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem)
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,9 @@ package im.vector.app.features.raw.wellknown
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
|
|
||||||
suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? {
|
suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? {
|
||||||
return tryOrNull { awaitCallback<String> { getWellknown(userId, it) } }
|
return tryOrNull { getWellknown(userId) }
|
||||||
?.let { ElementWellKnownMapper.from(it) }
|
?.let { ElementWellKnownMapper.from(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||||
|
|
||||||
sealed class RoomProfileAction : VectorViewModelAction {
|
sealed class RoomProfileAction : VectorViewModelAction {
|
||||||
|
object EnableEncryption : RoomProfileAction()
|
||||||
object LeaveRoom : RoomProfileAction()
|
object LeaveRoom : RoomProfileAction()
|
||||||
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
|
data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction()
|
||||||
object ShareRoomProfile : RoomProfileAction()
|
object ShareRoomProfile : RoomProfileAction()
|
||||||
|
|
|
@ -46,10 +46,16 @@ class RoomProfileActivity :
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newIntent(context: Context, roomId: String): Intent {
|
private const val EXTRA_DIRECT_ACCESS = "EXTRA_DIRECT_ACCESS"
|
||||||
|
|
||||||
|
const val EXTRA_DIRECT_ACCESS_ROOM_ROOT = 0
|
||||||
|
const val EXTRA_DIRECT_ACCESS_ROOM_SETTINGS = 1
|
||||||
|
|
||||||
|
fun newIntent(context: Context, roomId: String, directAccess: Int?): Intent {
|
||||||
val roomProfileArgs = RoomProfileArgs(roomId)
|
val roomProfileArgs = RoomProfileArgs(roomId)
|
||||||
return Intent(context, RoomProfileActivity::class.java).apply {
|
return Intent(context, RoomProfileActivity::class.java).apply {
|
||||||
putExtra(MvRx.KEY_ARG, roomProfileArgs)
|
putExtra(MvRx.KEY_ARG, roomProfileArgs)
|
||||||
|
putExtra(EXTRA_DIRECT_ACCESS, directAccess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +86,13 @@ class RoomProfileActivity :
|
||||||
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
|
sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java)
|
||||||
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
|
roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return
|
||||||
if (isFirstCreation()) {
|
if (isFirstCreation()) {
|
||||||
|
when (intent?.extras?.getInt(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOM_ROOT)) {
|
||||||
|
EXTRA_DIRECT_ACCESS_ROOM_SETTINGS -> {
|
||||||
addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
|
addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
|
||||||
|
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
|
||||||
|
}
|
||||||
|
else -> addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sharedActionViewModel
|
sharedActionViewModel
|
||||||
.observe()
|
.observe()
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.app.core.ui.list.genericFooterItem
|
||||||
import im.vector.app.features.home.ShortcutCreator
|
import im.vector.app.features.home.ShortcutCreator
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomProfileController @Inject constructor(
|
class RoomProfileController @Inject constructor(
|
||||||
|
@ -43,6 +44,7 @@ class RoomProfileController @Inject constructor(
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
fun onLearnMoreClicked()
|
fun onLearnMoreClicked()
|
||||||
|
fun onEnableEncryptionClicked()
|
||||||
fun onMemberListClicked()
|
fun onMemberListClicked()
|
||||||
fun onBannedMemberListClicked()
|
fun onBannedMemberListClicked()
|
||||||
fun onNotificationsClicked()
|
fun onNotificationsClicked()
|
||||||
|
@ -84,6 +86,7 @@ class RoomProfileController @Inject constructor(
|
||||||
centered(false)
|
centered(false)
|
||||||
text(stringProvider.getString(learnMoreSubtitle))
|
text(stringProvider.getString(learnMoreSubtitle))
|
||||||
}
|
}
|
||||||
|
buildEncryptionAction(data.actionPermissions, roomSummary)
|
||||||
|
|
||||||
// More
|
// More
|
||||||
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
|
||||||
|
@ -171,4 +174,29 @@ class RoomProfileController @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildEncryptionAction(actionPermissions: RoomProfileViewState.ActionPermissions, roomSummary: RoomSummary) {
|
||||||
|
if (!roomSummary.isEncrypted) {
|
||||||
|
if (actionPermissions.canEnableEncryption) {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "enableEncryption",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_enable_encryption),
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
icon = R.drawable.ic_shield_black,
|
||||||
|
divider = false,
|
||||||
|
editable = false,
|
||||||
|
action = { callback?.onEnableEncryptionClicked() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
buildProfileAction(
|
||||||
|
id = "enableEncryption",
|
||||||
|
title = stringProvider.getString(R.string.room_settings_enable_encryption_no_permission),
|
||||||
|
dividerColor = dividerColor,
|
||||||
|
icon = R.drawable.ic_shield_black,
|
||||||
|
divider = false,
|
||||||
|
editable = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
||||||
import im.vector.app.features.media.BigImageViewerActivity
|
import im.vector.app.features.media.BigImageViewerActivity
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
|
import kotlinx.android.synthetic.main.fragment_matrix_profile.*
|
||||||
|
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
|
||||||
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
|
import kotlinx.android.synthetic.main.view_stub_room_profile_header.*
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
@ -87,6 +88,7 @@ class RoomProfileFragment @Inject constructor(
|
||||||
it.layoutResource = R.layout.view_stub_room_profile_header
|
it.layoutResource = R.layout.view_stub_room_profile_header
|
||||||
it.inflate()
|
it.inflate()
|
||||||
}
|
}
|
||||||
|
setupWaitingView()
|
||||||
setupToolbar(matrixProfileToolbar)
|
setupToolbar(matrixProfileToolbar)
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(
|
appBarStateChangeListener = MatrixItemAppBarStateChangeListener(
|
||||||
|
@ -111,6 +113,11 @@ class RoomProfileFragment @Inject constructor(
|
||||||
setupLongClicks()
|
setupLongClicks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupWaitingView() {
|
||||||
|
waiting_view_status_text.setText(R.string.please_wait)
|
||||||
|
waiting_view_status_text.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupLongClicks() {
|
private fun setupLongClicks() {
|
||||||
roomProfileNameView.copyOnLongClick()
|
roomProfileNameView.copyOnLongClick()
|
||||||
roomProfileAliasView.copyOnLongClick()
|
roomProfileAliasView.copyOnLongClick()
|
||||||
|
@ -155,6 +162,8 @@ class RoomProfileFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate() = withState(roomProfileViewModel) { state ->
|
override fun invalidate() = withState(roomProfileViewModel) { state ->
|
||||||
|
waiting_view.isVisible = state.isLoading
|
||||||
|
|
||||||
state.roomSummary()?.also {
|
state.roomSummary()?.also {
|
||||||
if (it.membership.isLeft()) {
|
if (it.membership.isLeft()) {
|
||||||
Timber.w("The room has been left")
|
Timber.w("The room has been left")
|
||||||
|
@ -187,6 +196,17 @@ class RoomProfileFragment @Inject constructor(
|
||||||
vectorBaseActivity.notImplemented()
|
vectorBaseActivity.notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onEnableEncryptionClicked() {
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
|
||||||
|
.setMessage(R.string.room_settings_enable_encryption_dialog_content)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ ->
|
||||||
|
roomProfileViewModel.handle(RoomProfileAction.EnableEncryption)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMemberListClicked() {
|
override fun onMemberListClicked() {
|
||||||
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomMembers)
|
roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomMembers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,15 @@ import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.ShortcutCreator
|
import im.vector.app.features.home.ShortcutCreator
|
||||||
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
|
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.rx.RxRoom
|
import org.matrix.android.sdk.rx.RxRoom
|
||||||
import org.matrix.android.sdk.rx.rx
|
import org.matrix.android.sdk.rx.rx
|
||||||
import org.matrix.android.sdk.rx.unwrap
|
import org.matrix.android.sdk.rx.unwrap
|
||||||
|
@ -65,6 +68,7 @@ class RoomProfileViewModel @AssistedInject constructor(
|
||||||
val rxRoom = room.rx()
|
val rxRoom = room.rx()
|
||||||
observeRoomSummary(rxRoom)
|
observeRoomSummary(rxRoom)
|
||||||
observeBannedRoomMembers(rxRoom)
|
observeBannedRoomMembers(rxRoom)
|
||||||
|
observePermissions()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeRoomSummary(rxRoom: RxRoom) {
|
private fun observeRoomSummary(rxRoom: RxRoom) {
|
||||||
|
@ -82,8 +86,22 @@ class RoomProfileViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observePermissions() {
|
||||||
|
PowerLevelsObservableFactory(room)
|
||||||
|
.createObservable()
|
||||||
|
.subscribe {
|
||||||
|
val powerLevelsHelper = PowerLevelsHelper(it)
|
||||||
|
val permissions = RoomProfileViewState.ActionPermissions(
|
||||||
|
canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
||||||
|
)
|
||||||
|
setState { copy(actionPermissions = permissions) }
|
||||||
|
}
|
||||||
|
.disposeOnClear()
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(action: RoomProfileAction) {
|
override fun handle(action: RoomProfileAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
is RoomProfileAction.EnableEncryption -> handleEnableEncryption()
|
||||||
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
|
RoomProfileAction.LeaveRoom -> handleLeaveRoom()
|
||||||
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||||
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile()
|
||||||
|
@ -91,6 +109,24 @@ class RoomProfileViewModel @AssistedInject constructor(
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleEnableEncryption() {
|
||||||
|
postLoading(true)
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
val result = runCatching { room.enableEncryption() }
|
||||||
|
postLoading(false)
|
||||||
|
result.onFailure { failure ->
|
||||||
|
_viewEvents.post(RoomProfileViewEvents.Failure(failure))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postLoading(isLoading: Boolean) {
|
||||||
|
setState {
|
||||||
|
copy(isLoading = isLoading)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCreateShortcut() {
|
private fun handleCreateShortcut() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
withState { state ->
|
withState { state ->
|
||||||
|
@ -102,11 +138,13 @@ class RoomProfileViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {
|
private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) {
|
||||||
room.setRoomNotificationState(action.notificationState, object : MatrixCallback<Unit> {
|
viewModelScope.launch {
|
||||||
override fun onFailure(failure: Throwable) {
|
try {
|
||||||
|
room.setRoomNotificationState(action.notificationState)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(RoomProfileViewEvents.Failure(failure))
|
_viewEvents.post(RoomProfileViewEvents.Failure(failure))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLeaveRoom() {
|
private fun handleLeaveRoom() {
|
||||||
|
|
|
@ -26,8 +26,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
data class RoomProfileViewState(
|
data class RoomProfileViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val roomSummary: Async<RoomSummary> = Uninitialized,
|
val roomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val bannedMembership: Async<List<RoomMemberSummary>> = Uninitialized
|
val bannedMembership: Async<List<RoomMemberSummary>> = Uninitialized,
|
||||||
|
val actionPermissions: ActionPermissions = ActionPermissions(),
|
||||||
|
val isLoading: Boolean = false
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
|
||||||
|
|
||||||
|
data class ActionPermissions(
|
||||||
|
val canEnableEncryption: Boolean = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ sealed class RoomSettingsAction : VectorViewModelAction {
|
||||||
data class SetRoomTopic(val newTopic: String) : RoomSettingsAction()
|
data class SetRoomTopic(val newTopic: String) : RoomSettingsAction()
|
||||||
data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction()
|
data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction()
|
||||||
data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction()
|
data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction()
|
||||||
object EnableEncryption : RoomSettingsAction()
|
|
||||||
object Save : RoomSettingsAction()
|
object Save : RoomSettingsAction()
|
||||||
object Cancel : RoomSettingsAction()
|
object Cancel : RoomSettingsAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibi
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -44,7 +43,6 @@ class RoomSettingsController @Inject constructor(
|
||||||
// Delete the avatar, or cancel an avatar change
|
// Delete the avatar, or cancel an avatar change
|
||||||
fun onAvatarDelete()
|
fun onAvatarDelete()
|
||||||
fun onAvatarChange()
|
fun onAvatarChange()
|
||||||
fun onEnableEncryptionClicked()
|
|
||||||
fun onNameChanged(name: String)
|
fun onNameChanged(name: String)
|
||||||
fun onTopicChanged(topic: String)
|
fun onTopicChanged(topic: String)
|
||||||
fun onHistoryVisibilityClicked()
|
fun onHistoryVisibilityClicked()
|
||||||
|
@ -130,33 +128,6 @@ class RoomSettingsController @Inject constructor(
|
||||||
editable = data.actionPermissions.canChangeHistoryReadability,
|
editable = data.actionPermissions.canChangeHistoryReadability,
|
||||||
action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() }
|
action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() }
|
||||||
)
|
)
|
||||||
|
|
||||||
buildEncryptionAction(data.actionPermissions, roomSummary)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildEncryptionAction(actionPermissions: RoomSettingsViewState.ActionPermissions, roomSummary: RoomSummary) {
|
|
||||||
if (!actionPermissions.canEnableEncryption) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (roomSummary.isEncrypted) {
|
|
||||||
buildProfileAction(
|
|
||||||
id = "encryption",
|
|
||||||
title = stringProvider.getString(R.string.room_settings_addresses_e2e_enabled),
|
|
||||||
dividerColor = dividerColor,
|
|
||||||
divider = false,
|
|
||||||
editable = false
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
buildProfileAction(
|
|
||||||
id = "encryption",
|
|
||||||
title = stringProvider.getString(R.string.room_settings_enable_encryption),
|
|
||||||
subtitle = stringProvider.getString(R.string.room_settings_enable_encryption_warning),
|
|
||||||
dividerColor = dividerColor,
|
|
||||||
divider = false,
|
|
||||||
editable = true,
|
|
||||||
action = { callback?.onEnableEncryptionClicked() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun formatRoomHistoryVisibilityEvent(event: Event): String? {
|
private fun formatRoomHistoryVisibilityEvent(event: Event): String? {
|
||||||
|
|
|
@ -127,17 +127,6 @@ class RoomSettingsFragment @Inject constructor(
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnableEncryptionClicked() {
|
|
||||||
AlertDialog.Builder(requireActivity())
|
|
||||||
.setTitle(R.string.room_settings_enable_encryption_dialog_title)
|
|
||||||
.setMessage(R.string.room_settings_enable_encryption_dialog_content)
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ ->
|
|
||||||
viewModel.handle(RoomSettingsAction.EnableEncryption)
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNameChanged(name: String) {
|
override fun onNameChanged(name: String) {
|
||||||
viewModel.handle(RoomSettingsAction.SetRoomName(name))
|
viewModel.handle(RoomSettingsAction.SetRoomName(name))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,6 @@ import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
import io.reactivex.Completable
|
import io.reactivex.Completable
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -117,8 +116,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS),
|
EventType.STATE_ROOM_CANONICAL_ALIAS),
|
||||||
canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true,
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY),
|
EventType.STATE_ROOM_HISTORY_VISIBILITY)
|
||||||
canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION)
|
|
||||||
)
|
)
|
||||||
setState { copy(actionPermissions = permissions) }
|
setState { copy(actionPermissions = permissions) }
|
||||||
}
|
}
|
||||||
|
@ -141,7 +139,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
|
|
||||||
override fun handle(action: RoomSettingsAction) {
|
override fun handle(action: RoomSettingsAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomSettingsAction.EnableEncryption -> handleEnableEncryption()
|
|
||||||
is RoomSettingsAction.SetAvatarAction -> handleSetAvatarAction(action)
|
is RoomSettingsAction.SetAvatarAction -> handleSetAvatarAction(action)
|
||||||
is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) }
|
is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) }
|
||||||
is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) }
|
is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) }
|
||||||
|
@ -225,21 +222,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState:
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEnableEncryption() {
|
|
||||||
postLoading(true)
|
|
||||||
|
|
||||||
room.enableEncryption(callback = object : MatrixCallback<Unit> {
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
postLoading(false)
|
|
||||||
_viewEvents.post(RoomSettingsViewEvents.Failure(failure))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
postLoading(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun postLoading(isLoading: Boolean) {
|
private fun postLoading(isLoading: Boolean) {
|
||||||
setState {
|
setState {
|
||||||
copy(isLoading = isLoading)
|
copy(isLoading = isLoading)
|
||||||
|
|
|
@ -47,8 +47,7 @@ data class RoomSettingsViewState(
|
||||||
val canChangeName: Boolean = false,
|
val canChangeName: Boolean = false,
|
||||||
val canChangeTopic: Boolean = false,
|
val canChangeTopic: Boolean = false,
|
||||||
val canChangeCanonicalAlias: Boolean = false,
|
val canChangeCanonicalAlias: Boolean = false,
|
||||||
val canChangeHistoryReadability: Boolean = false,
|
val canChangeHistoryReadability: Boolean = false
|
||||||
val canEnableEncryption: Boolean = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
sealed class AvatarAction {
|
sealed class AvatarAction {
|
||||||
|
|
|
@ -15,12 +15,13 @@
|
||||||
*/
|
*/
|
||||||
package im.vector.app.features.settings
|
package im.vector.app.features.settings
|
||||||
|
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.preference.PushRulePreference
|
import im.vector.app.core.preference.PushRulePreference
|
||||||
import im.vector.app.core.preference.VectorPreference
|
import im.vector.app.core.preference.VectorPreference
|
||||||
import im.vector.app.core.utils.toast
|
import im.vector.app.core.utils.toast
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleIds
|
import org.matrix.android.sdk.api.pushrules.RuleIds
|
||||||
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
|
import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -50,29 +51,25 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor()
|
||||||
if (newRule != null) {
|
if (newRule != null) {
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
|
||||||
session.updatePushRuleActions(
|
lifecycleScope.launch {
|
||||||
ruleAndKind.kind,
|
val result = runCatching {
|
||||||
|
session.updatePushRuleActions(ruleAndKind.kind,
|
||||||
preference.ruleAndKind?.pushRule ?: ruleAndKind.pushRule,
|
preference.ruleAndKind?.pushRule ?: ruleAndKind.pushRule,
|
||||||
newRule,
|
newRule)
|
||||||
object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
if (!isAdded) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if (!isAdded) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
hideLoadingView()
|
||||||
|
result.onSuccess {
|
||||||
preference.setPushRule(ruleAndKind.copy(pushRule = newRule))
|
preference.setPushRule(ruleAndKind.copy(pushRule = newRule))
|
||||||
hideLoadingView()
|
|
||||||
}
|
}
|
||||||
|
result.onFailure { failure ->
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
if (!isAdded) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hideLoadingView()
|
|
||||||
// Restore the previous value
|
// Restore the previous value
|
||||||
refreshDisplay()
|
refreshDisplay()
|
||||||
activity?.toast(errorFormatter.toHumanReadable(failure))
|
activity?.toast(errorFormatter.toHumanReadable(failure))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import android.media.RingtoneManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.SwitchPreference
|
import androidx.preference.SwitchPreference
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
@ -37,6 +38,7 @@ import im.vector.app.core.utils.isIgnoringBatteryOptimizations
|
||||||
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
||||||
import im.vector.app.features.notifications.NotificationUtils
|
import im.vector.app.features.notifications.NotificationUtils
|
||||||
import im.vector.app.push.fcm.FcmHelper
|
import im.vector.app.push.fcm.FcmHelper
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleIds
|
import org.matrix.android.sdk.api.pushrules.RuleIds
|
||||||
|
@ -318,24 +320,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
||||||
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
|
.find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL }
|
||||||
?.let {
|
?.let {
|
||||||
// Trick, we must enable this room to disable notifications
|
// Trick, we must enable this room to disable notifications
|
||||||
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
pushRuleService.updatePushRuleEnableStatus(RuleKind.OVERRIDE,
|
pushRuleService.updatePushRuleEnableStatus(RuleKind.OVERRIDE,
|
||||||
it,
|
it,
|
||||||
!switchPref.isChecked,
|
!switchPref.isChecked)
|
||||||
object : MatrixCallback<Unit> {
|
|
||||||
override fun onSuccess(data: Unit) {
|
|
||||||
// Push rules will be updated from the sync
|
// Push rules will be updated from the sync
|
||||||
}
|
} catch (failure: Throwable) {
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
if (!isAdded) {
|
if (!isAdded) {
|
||||||
return
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// revert the check box
|
// revert the check box
|
||||||
switchPref.isChecked = !switchPref.isChecked
|
switchPref.isChecked = !switchPref.isChecked
|
||||||
Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ import androidx.activity.result.ActivityResultLauncher
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleIds
|
import org.matrix.android.sdk.api.pushrules.RuleIds
|
||||||
import org.matrix.android.sdk.api.pushrules.RuleKind
|
import org.matrix.android.sdk.api.pushrules.RuleKind
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -48,16 +49,12 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String
|
||||||
override fun doFix() {
|
override fun doFix() {
|
||||||
if (manager?.diagStatus == TestStatus.RUNNING) return // wait before all is finished
|
if (manager?.diagStatus == TestStatus.RUNNING) return // wait before all is finished
|
||||||
|
|
||||||
session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled,
|
GlobalScope.launch {
|
||||||
object : MatrixCallback<Unit> {
|
runCatching {
|
||||||
override fun onSuccess(data: Unit) {
|
session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled)
|
||||||
|
}
|
||||||
manager?.retry(activityResultLauncher)
|
manager?.retry(activityResultLauncher)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
manager?.retry(activityResultLauncher)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status = TestStatus.FAILED
|
status = TestStatus.FAILED
|
||||||
|
|
10
vector/src/main/res/drawable/ic_add_people.xml
Normal file
10
vector/src/main/res/drawable/ic_add_people.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M19.1001,9C18.7779,9 18.5168,8.7388 18.5168,8.4167V6.0833H16.1834C15.8613,6.0833 15.6001,5.8222 15.6001,5.5C15.6001,5.1778 15.8613,4.9167 16.1834,4.9167H18.5168V2.5833C18.5168,2.2612 18.7779,2 19.1001,2C19.4223,2 19.6834,2.2612 19.6834,2.5833V4.9167H22.0168C22.3389,4.9167 22.6001,5.1778 22.6001,5.5C22.6001,5.8222 22.3389,6.0833 22.0168,6.0833H19.6834V8.4167C19.6834,8.7388 19.4223,9 19.1001,9ZM19.6001,11C20.0669,11 20.5212,10.9467 20.9574,10.8458C21.1161,11.5383 21.2,12.2594 21.2,13C21.2,16.1409 19.6917,18.9294 17.3598,20.6808V20.6807C16.0014,21.7011 14.3635,22.3695 12.5815,22.5505C12.2588,22.5832 11.9314,22.6 11.6,22.6C6.2981,22.6 2,18.302 2,13C2,7.6981 6.2981,3.4 11.6,3.4C12.3407,3.4 13.0618,3.4839 13.7543,3.6427C13.6534,4.0788 13.6001,4.5332 13.6001,5C13.6001,8.3137 16.2864,11 19.6001,11ZM11.5999,20.68C13.6754,20.68 15.5585,19.8567 16.9407,18.5189C16.0859,16.4086 14.0167,14.92 11.5998,14.92C9.183,14.92 7.1138,16.4086 6.259,18.5189C7.6411,19.8567 9.5244,20.68 11.5999,20.68ZM11.7426,7.4117C10.3168,7.5417 9.2,8.7404 9.2,10.2C9.2,11.7464 10.4536,13 12,13C13.0308,13 13.9315,12.443 14.4176,11.6135C13.0673,10.6058 12.0929,9.1225 11.7426,7.4117Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
|
@ -93,6 +93,27 @@
|
||||||
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/phoneBookSearchForMatrixContacts"
|
||||||
|
style="@style/VectorButtonStyleText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/layout_horizontal_margin"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="@dimen/layout_horizontal_margin"
|
||||||
|
android:text="@string/phone_book_perform_lookup"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/phoneBookBottomBarrier"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="phoneBookSearchForMatrixContacts,phoneBookOnlyBoundContacts" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/phoneBookFilterDivider"
|
android:id="@+id/phoneBookFilterDivider"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -101,7 +122,7 @@
|
||||||
android:background="?attr/vctr_list_divider_color"
|
android:background="?attr/vctr_list_divider_color"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/phoneBookOnlyBoundContacts" />
|
app:layout_constraintTop_toBottomOf="@+id/phoneBookBottomBarrier" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/phoneBookRecyclerView"
|
android:id="@+id/phoneBookRecyclerView"
|
||||||
|
|
|
@ -95,7 +95,6 @@
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -105,4 +104,6 @@
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
tools:listitem="@layout/item_profile_action" />
|
tools:listitem="@layout/item_profile_action" />
|
||||||
|
|
||||||
|
<include layout="@layout/merge_overlay_waiting_view" />
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,31 +1,149 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/creationTile"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/creationEncryptionTile"
|
android:id="@+id/creationEncryptionTile"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="2dp"
|
android:layout_marginTop="2dp"
|
||||||
android:layout_marginEnd="52dp"
|
android:layout_marginEnd="52dp"
|
||||||
android:layout_marginBottom="2dp"
|
android:layout_marginBottom="2dp"
|
||||||
android:background="@drawable/rounded_rect_shape_8"
|
android:background="@drawable/rounded_rect_shape_8"
|
||||||
android:padding="8dp">
|
android:padding="8dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<include layout="@layout/item_timeline_event_status_tile_stub" />
|
<include layout="@layout/item_timeline_event_status_tile_stub" />
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/creationTileRoomAvatarImageView"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/creationEncryptionTile"
|
||||||
|
tools:srcCompat="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/creationTileSetAvatarButton"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:backgroundTint="?riotx_bottom_nav_background_color"
|
||||||
|
android:contentDescription="@string/room_settings_set_avatar"
|
||||||
|
android:elevation="2dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:padding="0dp"
|
||||||
|
app:cornerRadius="30dp"
|
||||||
|
app:icon="@drawable/ic_camera"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconPadding="0dp"
|
||||||
|
app:iconSize="20dp"
|
||||||
|
app:iconTint="?riot_primary_text_color"
|
||||||
|
app:layout_constraintCircle="@+id/creationTileRoomAvatarImageView"
|
||||||
|
app:layout_constraintCircleAngle="135"
|
||||||
|
app:layout_constraintCircleRadius="34dp"
|
||||||
|
tools:ignore="MissingConstraints" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/roomNameTileText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:textColor="?riotx_text_primary"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/creationTileRoomAvatarImageView"
|
||||||
|
tools:text="@sample/matrix.json/data/roomName" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/roomNameDescriptionText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="normal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomNameTileText"
|
||||||
|
tools:text="@string/this_is_the_beginning_of_room_no_name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/roomNameTopicText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textColorLink="@color/riotx_accent"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="normal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomNameDescriptionText"
|
||||||
|
tools:text="@string/room_created_summary_no_topic_creation_text" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/creationTileAddPeopleButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:contentDescription="@string/add_people"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/roomNameTopicText">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/addPeopleButtonBg"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/circle"
|
||||||
|
android:backgroundTint="@color/riotx_accent"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_add_people" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:text="@string/add_people"
|
||||||
|
android:textColor="@color/riotx_accent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/mergedSumContainer"
|
android:id="@+id/mergedSumContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/creationEncryptionTile">
|
android:layout_below="@+id/creationTile"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/itemNoticeAvatarView"
|
android:id="@+id/itemNoticeAvatarView"
|
||||||
|
|
|
@ -1793,6 +1793,14 @@
|
||||||
<string name="settings_discovery_confirm_mail">We sent you a confirm email to %s, check your email and click on the confirmation link</string>
|
<string name="settings_discovery_confirm_mail">We sent you a confirm email to %s, check your email and click on the confirmation link</string>
|
||||||
<string name="settings_discovery_confirm_mail_not_clicked">We sent you a confirm email to %s, please first check your email and click on the confirmation link</string>
|
<string name="settings_discovery_confirm_mail_not_clicked">We sent you a confirm email to %s, please first check your email and click on the confirmation link</string>
|
||||||
<string name="settings_discovery_mail_pending">Pending</string>
|
<string name="settings_discovery_mail_pending">Pending</string>
|
||||||
|
<string name="settings_discovery_consent_title">Send emails and phone numbers</string>
|
||||||
|
<string name="settings_discovery_consent_notice_on">You have given your consent to send emails and phone numbers to this identity server to discover other users from your contacts.</string>
|
||||||
|
<string name="settings_discovery_consent_notice_off">You have not given your consent to send emails and phone numbers to this identity server to discover other users from your contacts.</string>
|
||||||
|
<string name="settings_discovery_consent_action_revoke">Revoke my consent</string>
|
||||||
|
<string name="settings_discovery_consent_action_give_consent">Give consent</string>
|
||||||
|
|
||||||
|
<string name="identity_server_consent_dialog_title">Send emails and phone numbers</string>
|
||||||
|
<string name="identity_server_consent_dialog_content">In order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured Identity Server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent.</string>
|
||||||
|
|
||||||
<string name="settings_discovery_enter_identity_server">Enter an identity server URL</string>
|
<string name="settings_discovery_enter_identity_server">Enter an identity server URL</string>
|
||||||
<string name="settings_discovery_bad_identity_server">Could not connect to identity server</string>
|
<string name="settings_discovery_bad_identity_server">Could not connect to identity server</string>
|
||||||
|
@ -2192,7 +2200,8 @@
|
||||||
<!-- Title for category in the settings which affect the behavior of the message editor (ex: enable Markdown, send typing notification, etc.) -->
|
<!-- Title for category in the settings which affect the behavior of the message editor (ex: enable Markdown, send typing notification, etc.) -->
|
||||||
<string name="settings_category_composer">Message editor</string>
|
<string name="settings_category_composer">Message editor</string>
|
||||||
|
|
||||||
<string name="room_settings_enable_encryption">Enable end-to-end encryption</string>
|
<string name="room_settings_enable_encryption">Enable end-to-end encryption…</string>
|
||||||
|
<string name="room_settings_enable_encryption_no_permission">You don\'t have permission to enable encryption in this room.</string>
|
||||||
<string name="room_settings_enable_encryption_warning">Once enabled, encryption cannot be disabled.</string>
|
<string name="room_settings_enable_encryption_warning">Once enabled, encryption cannot be disabled.</string>
|
||||||
|
|
||||||
<string name="room_settings_enable_encryption_dialog_title">Enable encryption?</string>
|
<string name="room_settings_enable_encryption_dialog_title">Enable encryption?</string>
|
||||||
|
@ -2395,6 +2404,13 @@
|
||||||
<string name="room_created_summary_item_by_you">You created and configured the room.</string>
|
<string name="room_created_summary_item_by_you">You created and configured the room.</string>
|
||||||
<string name="direct_room_created_summary_item">%s joined.</string>
|
<string name="direct_room_created_summary_item">%s joined.</string>
|
||||||
<string name="direct_room_created_summary_item_by_you">You joined.</string>
|
<string name="direct_room_created_summary_item_by_you">You joined.</string>
|
||||||
|
<string name="this_is_the_beginning_of_room">This is the beginning of %s.</string>
|
||||||
|
<string name="this_is_the_beginning_of_room_no_name">This is the beginning of this conversation.</string>
|
||||||
|
<string name="this_is_the_beginning_of_dm">This is the beginning of your direct message history with %s.</string>
|
||||||
|
<!-- First param will be replaced by the value of add_a_topic_link_text, that will be clickable-->
|
||||||
|
<string name="room_created_summary_no_topic_creation_text">%s to let people know what this room is about.</string>
|
||||||
|
<string name="add_a_topic_link_text">Add a topic</string>
|
||||||
|
<string name="topic_prefix">"Topic: "</string>
|
||||||
|
|
||||||
<string name="qr_code_scanned_self_verif_notice">Almost there! Is the other device showing the same shield?</string>
|
<string name="qr_code_scanned_self_verif_notice">Almost there! Is the other device showing the same shield?</string>
|
||||||
<string name="qr_code_scanned_verif_waiting_notice">Almost there! Waiting for confirmation…</string>
|
<string name="qr_code_scanned_verif_waiting_notice">Almost there! Waiting for confirmation…</string>
|
||||||
|
@ -2503,6 +2519,7 @@
|
||||||
<string name="create_room_dm_failure">"We couldn't create your DM. Please check the users you want to invite and try again."</string>
|
<string name="create_room_dm_failure">"We couldn't create your DM. Please check the users you want to invite and try again."</string>
|
||||||
|
|
||||||
<string name="add_members_to_room">Add members</string>
|
<string name="add_members_to_room">Add members</string>
|
||||||
|
<string name="add_people">Add people</string>
|
||||||
<string name="invite_users_to_room_action_invite">INVITE</string>
|
<string name="invite_users_to_room_action_invite">INVITE</string>
|
||||||
<string name="inviting_users_to_room">Inviting users…</string>
|
<string name="inviting_users_to_room">Inviting users…</string>
|
||||||
<string name="invite_users_to_room_title">Invite Users</string>
|
<string name="invite_users_to_room_title">Invite Users</string>
|
||||||
|
@ -2527,6 +2544,7 @@
|
||||||
<string name="identity_server_error_bulk_sha256_not_supported">For your privacy, Element only supports sending hashed user emails and phone number.</string>
|
<string name="identity_server_error_bulk_sha256_not_supported">For your privacy, Element only supports sending hashed user emails and phone number.</string>
|
||||||
<string name="identity_server_error_binding_error">The association has failed.</string>
|
<string name="identity_server_error_binding_error">The association has failed.</string>
|
||||||
<string name="identity_server_error_no_current_binding_error">The is no current association with this identifier.</string>
|
<string name="identity_server_error_no_current_binding_error">The is no current association with this identifier.</string>
|
||||||
|
<string name="identity_server_user_consent_not_provided">The user consent has not been provided.</string>
|
||||||
|
|
||||||
<string name="identity_server_set_default_notice">Your homeserver (%1$s) proposes to use %2$s for your identity server</string>
|
<string name="identity_server_set_default_notice">Your homeserver (%1$s) proposes to use %2$s for your identity server</string>
|
||||||
<string name="identity_server_set_default_submit">Use %1$s</string>
|
<string name="identity_server_set_default_submit">Use %1$s</string>
|
||||||
|
@ -2566,6 +2584,8 @@
|
||||||
<string name="room_settings_name_hint">Room Name</string>
|
<string name="room_settings_name_hint">Room Name</string>
|
||||||
<string name="room_settings_topic_hint">Topic</string>
|
<string name="room_settings_topic_hint">Topic</string>
|
||||||
<string name="room_settings_save_success">You changed room settings successfully</string>
|
<string name="room_settings_save_success">You changed room settings successfully</string>
|
||||||
|
<string name="room_settings_set_avatar">Set avatar</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="notice_crypto_unable_to_decrypt_final">You cannot access this message</string>
|
<string name="notice_crypto_unable_to_decrypt_final">You cannot access this message</string>
|
||||||
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>
|
<string name="notice_crypto_unable_to_decrypt_friendly">Waiting for this message, this may take a while</string>
|
||||||
|
@ -2593,6 +2613,7 @@
|
||||||
<string name="loading_contact_book">Retrieving your contacts…</string>
|
<string name="loading_contact_book">Retrieving your contacts…</string>
|
||||||
<string name="empty_contact_book">Your contact book is empty</string>
|
<string name="empty_contact_book">Your contact book is empty</string>
|
||||||
<string name="contacts_book_title">Contacts book</string>
|
<string name="contacts_book_title">Contacts book</string>
|
||||||
|
<string name="phone_book_perform_lookup">Search for contacts on Matrix</string>
|
||||||
|
|
||||||
<string name="three_pid_revoke_invite_dialog_title">Revoke invite</string>
|
<string name="three_pid_revoke_invite_dialog_title">Revoke invite</string>
|
||||||
<string name="three_pid_revoke_invite_dialog_content">Revoke invite to %1$s?</string>
|
<string name="three_pid_revoke_invite_dialog_content">Revoke invite to %1$s?</string>
|
||||||
|
|
Loading…
Reference in a new issue