Merge branch 'develop' into feature/bma/fix_crash
This commit is contained in:
commit
675e4579ac
11
AUTHORS.md
11
AUTHORS.md
@ -4,7 +4,7 @@ A full developer contributors list can be found [here](https://github.com/vector
|
|||||||
|
|
||||||
Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves.
|
Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves.
|
||||||
|
|
||||||
## Benoit: Android team leader
|
## [Benoit](https://github.com/bmarty): Android team leader
|
||||||
|
|
||||||
[@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org)
|
[@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org)
|
||||||
- Android team leader and project leader, Android developer, GitHub community manager.
|
- Android team leader and project leader, Android developer, GitHub community manager.
|
||||||
@ -12,7 +12,7 @@ Even if we try to be able to work on all the functionalities, we have more knowl
|
|||||||
- Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager.
|
- Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager.
|
||||||
- Release manager on the Play Store
|
- Release manager on the Play Store
|
||||||
|
|
||||||
## François: Software architect
|
## [Ganfra](https://github.com/ganfra) (aka François): Software architect
|
||||||
|
|
||||||
[@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org)
|
[@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org)
|
||||||
- Software architect, Android developer
|
- Software architect, Android developer
|
||||||
@ -20,12 +20,17 @@ Even if we try to be able to work on all the functionalities, we have more knowl
|
|||||||
- Work mainly on the global architecture of the project.
|
- Work mainly on the global architecture of the project.
|
||||||
- Specialist of the timeline, and lots of other features.
|
- Specialist of the timeline, and lots of other features.
|
||||||
|
|
||||||
## Valere: Product manager, Android developer
|
## [Valere](https://github.com/BillCarsonFr): Product manager, Android developer
|
||||||
|
|
||||||
[@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org)
|
[@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org)
|
||||||
- Product manager, Android developer
|
- Product manager, Android developer
|
||||||
- Specialist on the crypto implementation.
|
- Specialist on the crypto implementation.
|
||||||
|
|
||||||
|
## [Onuray](https://github.com/onurays): Android developer
|
||||||
|
|
||||||
|
[@onurays:matrix.org](https://matrix.to/#/@onurays:matrix.org)
|
||||||
|
- Android developer
|
||||||
|
|
||||||
# Other contributors
|
# Other contributors
|
||||||
|
|
||||||
First of all, we thank all contributors who use Element and report problems on this GitHub project or via the integrated rageshake function.
|
First of all, we thank all contributors who use Element and report problems on this GitHub project or via the integrated rageshake function.
|
||||||
|
10
CHANGES.md
10
CHANGES.md
@ -5,12 +5,22 @@ 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.
|
||||||
|
- 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 🐛:
|
||||||
- Fix crash on AttachmentViewer (#2365)
|
- Fix crash on AttachmentViewer (#2365)
|
||||||
|
- 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
|
||||||
|
- Try to fix cropped image in timeline (#2126)
|
||||||
|
- 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
|
||||||
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
|
|||||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
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.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
@ -92,6 +93,13 @@ class RxSession(private val session: Session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveRoomMember(userId: String, roomId: String): Observable<Optional<RoomMemberSummary>> {
|
||||||
|
return session.getRoomMemberLive(userId, roomId).asObservable()
|
||||||
|
.startWithCallable {
|
||||||
|
session.getRoomMember(userId, roomId).toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun liveUsers(): Observable<List<User>> {
|
fun liveUsers(): Observable<List<User>> {
|
||||||
return session.getUsersLive().asObservable()
|
return session.getUsersLive().asObservable()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.room
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
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.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
@ -141,4 +142,20 @@ interface RoomService {
|
|||||||
* - the power level of the users are not taken into account. Normally in a DM, the 2 members are admins of the room
|
* - the power level of the users are not taken into account. Normally in a DM, the 2 members are admins of the room
|
||||||
*/
|
*/
|
||||||
fun getExistingDirectRoomWithUser(otherUserId: String): String?
|
fun getExistingDirectRoomWithUser(otherUserId: String): String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a room member for the tuple {userId,roomId}
|
||||||
|
* @param userId the userId to look for.
|
||||||
|
* @param roomId the roomId to look for.
|
||||||
|
* @return the room member or null
|
||||||
|
*/
|
||||||
|
fun getRoomMember(userId: String, roomId: String): RoomMemberSummary?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observe a live room member for the tuple {userId,roomId}
|
||||||
|
* @param userId the userId to look for.
|
||||||
|
* @param roomId the roomId to look for.
|
||||||
|
* @return a LiveData of the optional found room member
|
||||||
|
*/
|
||||||
|
fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>>
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,27 +17,37 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room
|
package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Transformations
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
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.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
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.api.util.toOptional
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||||
|
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||||
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
|
import org.matrix.android.sdk.internal.util.fetchCopied
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultRoomService @Inject constructor(
|
internal class DefaultRoomService @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val createRoomTask: CreateRoomTask,
|
private val createRoomTask: CreateRoomTask,
|
||||||
private val joinRoomTask: JoinRoomTask,
|
private val joinRoomTask: JoinRoomTask,
|
||||||
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
private val markAllRoomsReadTask: MarkAllRoomsReadTask,
|
||||||
@ -118,4 +128,24 @@ internal class DefaultRoomService @Inject constructor(
|
|||||||
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
|
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
|
||||||
return roomChangeMembershipStateDataSource.getLiveStates()
|
return roomChangeMembershipStateDataSource.getLiveStates()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRoomMember(userId: String, roomId: String): RoomMemberSummary? {
|
||||||
|
val roomMemberEntity = monarchy.fetchCopied {
|
||||||
|
RoomMemberHelper(it, roomId).getLastRoomMember(userId)
|
||||||
|
}
|
||||||
|
return roomMemberEntity?.asDomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>> {
|
||||||
|
val liveData = monarchy.findAllMappedWithChanges(
|
||||||
|
{ realm ->
|
||||||
|
RoomMemberHelper(realm, roomId).queryRoomMembersEvent()
|
||||||
|
.equalTo(RoomMemberSummaryEntityFields.USER_ID, userId)
|
||||||
|
},
|
||||||
|
{ it.asDomain() }
|
||||||
|
)
|
||||||
|
return Transformations.map(liveData) { results ->
|
||||||
|
results.firstOrNull().toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
echo "Configure Element Template..."
|
echo "Configure Element Template..."
|
||||||
if [ -z ${ANDROID_STUDIO+x} ]; then ANDROID_STUDIO="/Applications/Android Studio.app/Contents"; fi
|
if [ -z ${ANDROID_STUDIO+x} ]; then ANDROID_STUDIO="/Applications/Android Studio.app/Contents"; fi
|
||||||
{
|
{
|
||||||
|
mkdir -p "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other"
|
||||||
ln -s $(pwd)/ElementFeature "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other"
|
ln -s $(pwd)/ElementFeature "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other"
|
||||||
} && {
|
} && {
|
||||||
echo "Please restart Android Studio."
|
echo "Please restart Android Studio."
|
||||||
|
24
tools/templates/unconfigure.sh
Executable file
24
tools/templates/unconfigure.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2020 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Template prevent from upgrading Android Studio, so this script de configure the template
|
||||||
|
echo "Un-configure Element Template..."
|
||||||
|
if [ -z ${ANDROID_STUDIO+x} ]; then ANDROID_STUDIO="/Applications/Android Studio.app/Contents"; fi
|
||||||
|
|
||||||
|
rm "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other/ElementFeature"
|
||||||
|
rm -r "${ANDROID_STUDIO%/}/plugins/android/lib/templates"
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,12 +97,9 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||||||
unrecognizedCertificateDialog = screenComponent.unrecognizedCertificateDialog()
|
unrecognizedCertificateDialog = screenComponent.unrecognizedCertificateDialog()
|
||||||
viewModelFactory = screenComponent.viewModelFactory()
|
viewModelFactory = screenComponent.viewModelFactory()
|
||||||
childFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
childFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
||||||
injectWith(injector())
|
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun injectWith(injector: ScreenComponent) = Unit
|
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -137,7 +137,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||||||
session.callSignalingService().getCallWithId(it)?.let { mxCall ->
|
session.callSignalingService().getCallWithId(it)?.let { mxCall ->
|
||||||
this.call = mxCall
|
this.call = mxCall
|
||||||
mxCall.otherUserId
|
mxCall.otherUserId
|
||||||
val item: MatrixItem? = session.getUser(mxCall.otherUserId)?.toMatrixItem()
|
val item: MatrixItem? = session.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()
|
||||||
|
|
||||||
mxCall.addListener(callStateListener)
|
mxCall.addListener(callStateListener)
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
|||||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.webrtc.AudioSource
|
import org.webrtc.AudioSource
|
||||||
import org.webrtc.AudioTrack
|
import org.webrtc.AudioTrack
|
||||||
import org.webrtc.Camera1Enumerator
|
import org.webrtc.Camera1Enumerator
|
||||||
@ -330,8 +331,8 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
currentCall?.mxCall
|
currentCall?.mxCall
|
||||||
?.takeIf { it.state is CallState.Connected }
|
?.takeIf { it.state is CallState.Connected }
|
||||||
?.let { mxCall ->
|
?.let { mxCall ->
|
||||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
|
||||||
?: mxCall.roomId
|
?: mxCall.otherUserId
|
||||||
// Start background service with notification
|
// Start background service with notification
|
||||||
CallService.onPendingCall(
|
CallService.onPendingCall(
|
||||||
context = context,
|
context = context,
|
||||||
@ -388,7 +389,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
val mxCall = callContext.mxCall
|
val mxCall = callContext.mxCall
|
||||||
// Update service state
|
// Update service state
|
||||||
|
|
||||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
|
||||||
?: mxCall.roomId
|
?: mxCall.roomId
|
||||||
CallService.onPendingCall(
|
CallService.onPendingCall(
|
||||||
context = context,
|
context = context,
|
||||||
@ -576,7 +577,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
?.let { mxCall ->
|
?.let { mxCall ->
|
||||||
// Start background service with notification
|
// Start background service with notification
|
||||||
|
|
||||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
|
||||||
?: mxCall.otherUserId
|
?: mxCall.otherUserId
|
||||||
CallService.onOnGoingCallBackground(
|
CallService.onOnGoingCallBackground(
|
||||||
context = context,
|
context = context,
|
||||||
@ -650,7 +651,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
callAudioManager.startForCall(createdCall)
|
callAudioManager.startForCall(createdCall)
|
||||||
currentCall = callContext
|
currentCall = callContext
|
||||||
|
|
||||||
val name = currentSession?.getUser(createdCall.otherUserId)?.getBestName()
|
val name = currentSession?.getRoomMember(createdCall.otherUserId, createdCall.roomId)?.toMatrixItem()?.getBestName()
|
||||||
?: createdCall.otherUserId
|
?: createdCall.otherUserId
|
||||||
CallService.onOutgoingCallRinging(
|
CallService.onOutgoingCallRinging(
|
||||||
context = context.applicationContext,
|
context = context.applicationContext,
|
||||||
@ -706,7 +707,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start background service with notification
|
// Start background service with notification
|
||||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
|
||||||
?: mxCall.otherUserId
|
?: mxCall.otherUserId
|
||||||
CallService.onIncomingCallRinging(
|
CallService.onIncomingCallRinging(
|
||||||
context = context,
|
context = context,
|
||||||
@ -845,7 +846,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||||||
}
|
}
|
||||||
val mxCall = call.mxCall
|
val mxCall = call.mxCall
|
||||||
// Update service state
|
// Update service state
|
||||||
val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName()
|
val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName()
|
||||||
?: mxCall.otherUserId
|
?: mxCall.otherUserId
|
||||||
CallService.onPendingCall(
|
CallService.onPendingCall(
|
||||||
context = context,
|
context = context,
|
||||||
|
@ -46,7 +46,7 @@ class JitsiCallViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val me = session.getUser(session.myUserId)?.toMatrixItem()
|
val me = session.getRoomMember(session.myUserId, args.roomId)?.toMatrixItem()
|
||||||
val userInfo = JitsiMeetUserInfo().apply {
|
val userInfo = JitsiMeetUserInfo().apply {
|
||||||
displayName = me?.getBestName()
|
displayName = me?.getBestName()
|
||||||
avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) }
|
avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) }
|
||||||
|
@ -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
|
||||||
@ -141,6 +143,7 @@ import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsB
|
|||||||
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
import im.vector.app.features.html.PillImageSpan
|
import im.vector.app.features.html.PillImageSpan
|
||||||
|
import im.vector.app.features.html.PillsPostProcessor
|
||||||
import im.vector.app.features.invite.VectorInviteView
|
import im.vector.app.features.invite.VectorInviteView
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
import im.vector.app.features.media.VideoContentRenderer
|
import im.vector.app.features.media.VideoContentRenderer
|
||||||
@ -149,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
|
||||||
@ -196,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
|
||||||
|
|
||||||
@ -221,7 +226,8 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
|
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
|
||||||
private val matrixItemColorProvider: MatrixItemColorProvider,
|
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||||
private val imageContentRenderer: ImageContentRenderer,
|
private val imageContentRenderer: ImageContentRenderer,
|
||||||
private val roomDetailPendingActionStore: RoomDetailPendingActionStore
|
private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
|
||||||
|
private val pillsPostProcessorFactory: PillsPostProcessor.Factory
|
||||||
) :
|
) :
|
||||||
VectorBaseFragment(),
|
VectorBaseFragment(),
|
||||||
TimelineEventController.Callback,
|
TimelineEventController.Callback,
|
||||||
@ -229,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 {
|
||||||
@ -250,10 +256,15 @@ 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)
|
||||||
}
|
}
|
||||||
|
private val pillsPostProcessor by lazy {
|
||||||
|
pillsPostProcessorFactory.create(roomDetailArgs.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
private val autoCompleter: AutoCompleter by lazy {
|
private val autoCompleter: AutoCompleter by lazy {
|
||||||
autoCompleterFactory.create(roomDetailArgs.roomId)
|
autoCompleterFactory.create(roomDetailArgs.roomId)
|
||||||
@ -364,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,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)
|
||||||
}
|
}
|
||||||
@ -848,7 +883,7 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
|
||||||
val parser = Parser.builder().build()
|
val parser = Parser.builder().build()
|
||||||
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
|
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
|
||||||
formattedBody = eventHtmlRenderer.render(document)
|
formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
|
||||||
}
|
}
|
||||||
composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
|
composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
@ -1300,7 +1337,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
if (summary.membership == Membership.INVITE) {
|
if (summary.membership == Membership.INVITE) {
|
||||||
summary.inviterId?.let { inviterId ->
|
summary.inviterId?.let { inviterId ->
|
||||||
session.getUser(inviterId)
|
session.getRoomMember(inviterId, summary.roomId)
|
||||||
}?.also {
|
}?.also {
|
||||||
setState { copy(asyncInviter = Success(it)) }
|
setState { copy(asyncInviter = Success(it)) }
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
|||||||
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.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +59,7 @@ data class RoomDetailViewState(
|
|||||||
val roomId: String,
|
val roomId: String,
|
||||||
val eventId: String?,
|
val eventId: String?,
|
||||||
val myRoomMember: Async<RoomMemberSummary> = Uninitialized,
|
val myRoomMember: Async<RoomMemberSummary> = Uninitialized,
|
||||||
val asyncInviter: Async<User> = Uninitialized,
|
val asyncInviter: Async<RoomMemberSummary> = Uninitialized,
|
||||||
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
|
||||||
val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
|
val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
|
||||||
val typingMessage: String? = null,
|
val typingMessage: String? = null,
|
||||||
|
@ -123,7 +123,7 @@ class SearchResultController @Inject constructor(
|
|||||||
.formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE))
|
.formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE))
|
||||||
.spannable(spannable)
|
.spannable(spannable)
|
||||||
.sender(eventAndSender.sender
|
.sender(eventAndSender.sender
|
||||||
?: eventAndSender.event.senderId?.let { session.getUser(it) }?.toMatrixItem())
|
?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem())
|
||||||
.listener { listener?.onItemClicked(eventAndSender.event) }
|
.listener { listener?.onItemClicked(eventAndSender.event) }
|
||||||
.let { result.add(it) }
|
.let { result.add(it) }
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ 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.room.detail.timeline.format.NoticeEventFormatter
|
import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.app.features.html.PillsPostProcessor
|
||||||
import im.vector.app.features.html.VectorHtmlCompressor
|
import im.vector.app.features.html.VectorHtmlCompressor
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||||
import im.vector.app.features.reactions.data.EmojiDataSource
|
import im.vector.app.features.reactions.data.EmojiDataSource
|
||||||
@ -57,18 +58,22 @@ import java.util.ArrayList
|
|||||||
* Information related to an event and used to display preview in contextual bottom sheet.
|
* Information related to an event and used to display preview in contextual bottom sheet.
|
||||||
*/
|
*/
|
||||||
class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
||||||
initialState: MessageActionState,
|
private val initialState: MessageActionState,
|
||||||
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
|
private val eventHtmlRenderer: Lazy<EventHtmlRenderer>,
|
||||||
private val htmlCompressor: VectorHtmlCompressor,
|
private val htmlCompressor: VectorHtmlCompressor,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
private val noticeEventFormatter: NoticeEventFormatter,
|
private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
|
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||||
private val vectorPreferences: VectorPreferences
|
private val vectorPreferences: VectorPreferences
|
||||||
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
) : VectorViewModel<MessageActionState, MessageActionsAction, EmptyViewEvents>(initialState) {
|
||||||
|
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
private val informationData = initialState.informationData
|
private val informationData = initialState.informationData
|
||||||
private val room = session.getRoom(initialState.roomId)
|
private val room = session.getRoom(initialState.roomId)
|
||||||
|
private val pillsPostProcessor by lazy {
|
||||||
|
pillsPostProcessorFactory.create(initialState.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
@ -172,7 +177,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
|
|||||||
?.let { htmlCompressor.compress(it) }
|
?.let { htmlCompressor.compress(it) }
|
||||||
?: messageContent.body
|
?: messageContent.body
|
||||||
|
|
||||||
eventHtmlRenderer.get().render(html)
|
eventHtmlRenderer.get().render(html, pillsPostProcessor)
|
||||||
} else if (messageContent is MessageVerificationRequestContent) {
|
} else if (messageContent is MessageVerificationRequestContent) {
|
||||||
stringProvider.getString(R.string.verification_request)
|
stringProvider.getString(R.string.verification_request)
|
||||||
} else {
|
} else {
|
||||||
@ -186,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))
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement
|
|||||||
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
import im.vector.app.features.home.room.detail.timeline.tools.linkify
|
||||||
import im.vector.app.features.html.CodeVisitor
|
import im.vector.app.features.html.CodeVisitor
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.app.features.html.PillsPostProcessor
|
||||||
import im.vector.app.features.html.VectorHtmlCompressor
|
import im.vector.app.features.html.VectorHtmlCompressor
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
import im.vector.app.features.media.VideoContentRenderer
|
import im.vector.app.features.media.VideoContentRenderer
|
||||||
@ -106,15 +107,19 @@ class MessageItemFactory @Inject constructor(
|
|||||||
private val defaultItemFactory: DefaultItemFactory,
|
private val defaultItemFactory: DefaultItemFactory,
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
|
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||||
private val session: Session) {
|
private val session: Session) {
|
||||||
|
|
||||||
|
private val pillsPostProcessor by lazy {
|
||||||
|
pillsPostProcessorFactory.create(roomSummaryHolder.roomSummary?.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
highlight: Boolean,
|
highlight: Boolean,
|
||||||
callback: TimelineEventController.Callback?
|
callback: TimelineEventController.Callback?
|
||||||
): VectorEpoxyModel<*>? {
|
): VectorEpoxyModel<*>? {
|
||||||
event.root.eventId ?: return null
|
event.root.eventId ?: return null
|
||||||
|
|
||||||
val informationData = messageInformationDataFactory.create(event, nextEvent)
|
val informationData = messageInformationDataFactory.create(event, nextEvent)
|
||||||
|
|
||||||
if (event.root.isRedacted()) {
|
if (event.root.isRedacted()) {
|
||||||
@ -217,13 +222,17 @@ class MessageItemFactory @Inject constructor(
|
|||||||
attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
|
attributes: AbsMessageItem.Attributes): VerificationRequestItem? {
|
||||||
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
// If this request is not sent by me or sent to me, we should ignore it in timeline
|
||||||
val myUserId = session.myUserId
|
val myUserId = session.myUserId
|
||||||
|
val roomId = roomSummaryHolder.roomSummary?.roomId
|
||||||
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId
|
val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId
|
||||||
val otherUserName = if (informationData.sentByMe) session.getUser(messageContent.toUserId)?.displayName
|
val otherUserName = if (informationData.sentByMe) {
|
||||||
else informationData.memberName
|
session.getRoomMember(messageContent.toUserId, roomId ?: "")?.displayName
|
||||||
|
} else {
|
||||||
|
informationData.memberName
|
||||||
|
}
|
||||||
return VerificationRequestItem_()
|
return VerificationRequestItem_()
|
||||||
.attributes(
|
.attributes(
|
||||||
VerificationRequestItem.Attributes(
|
VerificationRequestItem.Attributes(
|
||||||
@ -393,7 +402,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
callback: TimelineEventController.Callback?,
|
callback: TimelineEventController.Callback?,
|
||||||
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
attributes: AbsMessageItem.Attributes): MessageTextItem? {
|
||||||
val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
|
val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
|
||||||
val formattedBody = htmlRenderer.get().render(compressed)
|
val formattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor)
|
||||||
return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,7 +537,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
|
private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence {
|
||||||
return matrixFormattedBody
|
return matrixFormattedBody
|
||||||
?.let { htmlCompressor.compress(it) }
|
?.let { htmlCompressor.compress(it) }
|
||||||
?.let { htmlRenderer.get().render(it) }
|
?.let { htmlRenderer.get().render(it, pillsPostProcessor) }
|
||||||
?: body
|
?: body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -17,21 +17,23 @@
|
|||||||
package im.vector.app.features.html
|
package im.vector.app.features.html
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import android.text.Spannable
|
||||||
import im.vector.app.core.glide.GlideApp
|
import androidx.core.text.toSpannable
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
import io.noties.markwon.html.HtmlPlugin
|
import io.noties.markwon.html.HtmlPlugin
|
||||||
import io.noties.markwon.html.TagHandlerNoOp
|
|
||||||
import org.commonmark.node.Node
|
import org.commonmark.node.Node
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class EventHtmlRenderer @Inject constructor(context: Context,
|
class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfigure,
|
||||||
htmlConfigure: MatrixHtmlPluginConfigure) {
|
context: Context) {
|
||||||
|
|
||||||
|
interface PostProcessor {
|
||||||
|
fun afterRender(renderedText: Spannable)
|
||||||
|
}
|
||||||
|
|
||||||
private val markwon = Markwon.builder(context)
|
private val markwon = Markwon.builder(context)
|
||||||
.usePlugin(HtmlPlugin.create(htmlConfigure))
|
.usePlugin(HtmlPlugin.create(htmlConfigure))
|
||||||
@ -41,35 +43,47 @@ class EventHtmlRenderer @Inject constructor(context: Context,
|
|||||||
return markwon.parse(text)
|
return markwon.parse(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(text: String): CharSequence {
|
/**
|
||||||
|
* @param text the text you want to render
|
||||||
|
* @param postProcessors an optional array of post processor to add any span if needed
|
||||||
|
*/
|
||||||
|
fun render(text: String, vararg postProcessors: PostProcessor): CharSequence {
|
||||||
return try {
|
return try {
|
||||||
markwon.toMarkdown(text)
|
val parsed = markwon.parse(text)
|
||||||
|
renderAndProcess(parsed, postProcessors)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.v("Fail to render $text to html")
|
Timber.v("Fail to render $text to html")
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(node: Node): CharSequence? {
|
/**
|
||||||
|
* @param node the node you want to render
|
||||||
|
* @param postProcessors an optional array of post processor to add any span if needed
|
||||||
|
*/
|
||||||
|
fun render(node: Node, vararg postProcessors: PostProcessor): CharSequence? {
|
||||||
return try {
|
return try {
|
||||||
markwon.render(node)
|
renderAndProcess(node, postProcessors)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.v("Fail to render $node to html")
|
Timber.v("Fail to render $node to html")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderAndProcess(node: Node, postProcessors: Array<out PostProcessor>): CharSequence {
|
||||||
|
val renderedText = markwon.render(node).toSpannable()
|
||||||
|
postProcessors.forEach {
|
||||||
|
it.afterRender(renderedText)
|
||||||
|
}
|
||||||
|
return renderedText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MatrixHtmlPluginConfigure @Inject constructor(private val context: Context,
|
class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: ColorProvider) : HtmlPlugin.HtmlConfigure {
|
||||||
private val colorProvider: ColorProvider,
|
|
||||||
private val avatarRenderer: AvatarRenderer,
|
|
||||||
private val session: ActiveSessionHolder) : HtmlPlugin.HtmlConfigure {
|
|
||||||
|
|
||||||
override fun configureHtml(plugin: HtmlPlugin) {
|
override fun configureHtml(plugin: HtmlPlugin) {
|
||||||
plugin
|
plugin
|
||||||
.addHandler(TagHandlerNoOp.create("a"))
|
|
||||||
.addHandler(FontTagHandler())
|
.addHandler(FontTagHandler())
|
||||||
.addHandler(MxLinkTagHandler(GlideApp.with(context), context, avatarRenderer, session))
|
|
||||||
.addHandler(MxReplyTagHandler())
|
.addHandler(MxReplyTagHandler())
|
||||||
.addHandler(SpanHandler(colorProvider))
|
.addHandler(SpanHandler(colorProvider))
|
||||||
}
|
}
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 New Vector Ltd
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package im.vector.app.features.html
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.text.style.URLSpan
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
|
||||||
import im.vector.app.core.glide.GlideRequests
|
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
|
||||||
import io.noties.markwon.MarkwonVisitor
|
|
||||||
import io.noties.markwon.SpannableBuilder
|
|
||||||
import io.noties.markwon.html.HtmlTag
|
|
||||||
import io.noties.markwon.html.MarkwonHtmlRenderer
|
|
||||||
import io.noties.markwon.html.tag.LinkHandler
|
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
|
||||||
|
|
||||||
class MxLinkTagHandler(private val glideRequests: GlideRequests,
|
|
||||||
private val context: Context,
|
|
||||||
private val avatarRenderer: AvatarRenderer,
|
|
||||||
private val sessionHolder: ActiveSessionHolder) : LinkHandler() {
|
|
||||||
|
|
||||||
override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) {
|
|
||||||
val link = tag.attributes()["href"]
|
|
||||||
if (link != null) {
|
|
||||||
val permalinkData = PermalinkParser.parse(link)
|
|
||||||
val matrixItem = when (permalinkData) {
|
|
||||||
is PermalinkData.UserLink -> {
|
|
||||||
val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)
|
|
||||||
MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl)
|
|
||||||
}
|
|
||||||
is PermalinkData.RoomLink -> {
|
|
||||||
if (permalinkData.eventId == null) {
|
|
||||||
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias)
|
|
||||||
if (permalinkData.isRoomAlias) {
|
|
||||||
MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
|
||||||
} else {
|
|
||||||
MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Exclude event link (used in reply events, we do not want to pill the "in reply to")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is PermalinkData.GroupLink -> {
|
|
||||||
val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId)
|
|
||||||
MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl)
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matrixItem == null) {
|
|
||||||
super.handle(visitor, renderer, tag)
|
|
||||||
} else {
|
|
||||||
val span = PillImageSpan(glideRequests, avatarRenderer, context, matrixItem)
|
|
||||||
SpannableBuilder.setSpans(
|
|
||||||
visitor.builder(),
|
|
||||||
span,
|
|
||||||
tag.start(),
|
|
||||||
tag.end()
|
|
||||||
)
|
|
||||||
SpannableBuilder.setSpans(
|
|
||||||
visitor.builder(),
|
|
||||||
URLSpan(link),
|
|
||||||
tag.start(),
|
|
||||||
tag.end()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.handle(visitor, renderer, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.html
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.Spanned
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.core.glide.GlideApp
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import io.noties.markwon.core.spans.LinkSpan
|
||||||
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||||
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
|
||||||
|
class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomId: String?,
|
||||||
|
private val context: Context,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val sessionHolder: ActiveSessionHolder)
|
||||||
|
: EventHtmlRenderer.PostProcessor {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String?): PillsPostProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterRender(renderedText: Spannable) {
|
||||||
|
addPillSpans(renderedText, roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addPillSpans(renderedText: Spannable, roomId: String?) {
|
||||||
|
// We let markdown handle links and then we add PillImageSpan if needed.
|
||||||
|
val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java)
|
||||||
|
linkSpans.forEach { linkSpan ->
|
||||||
|
val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach
|
||||||
|
val startSpan = renderedText.getSpanStart(linkSpan)
|
||||||
|
val endSpan = renderedText.getSpanEnd(linkSpan)
|
||||||
|
renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? {
|
||||||
|
val permalinkData = PermalinkParser.parse(url)
|
||||||
|
val matrixItem = when (permalinkData) {
|
||||||
|
is PermalinkData.UserLink -> {
|
||||||
|
if (roomId == null) {
|
||||||
|
sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)?.toMatrixItem()
|
||||||
|
} else {
|
||||||
|
sessionHolder.getSafeActiveSession()?.getRoomMember(permalinkData.userId, roomId)?.toMatrixItem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PermalinkData.RoomLink -> {
|
||||||
|
if (permalinkData.eventId == null) {
|
||||||
|
val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias)
|
||||||
|
if (permalinkData.isRoomAlias) {
|
||||||
|
MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
||||||
|
} else {
|
||||||
|
MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Exclude event link (used in reply events, we do not want to pill the "in reply to")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PermalinkData.GroupLink -> {
|
||||||
|
val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId)
|
||||||
|
MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
} ?: return null
|
||||||
|
return PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,7 @@ import im.vector.app.core.platform.ButtonStateView
|
|||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import kotlinx.android.synthetic.main.vector_invite_view.view.*
|
import kotlinx.android.synthetic.main.vector_invite_view.view.*
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.user.model.User
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun render(sender: User, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) {
|
fun render(sender: RoomMemberSummary, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) {
|
||||||
if (mode == Mode.LARGE) {
|
if (mode == Mode.LARGE) {
|
||||||
updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT }
|
updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT }
|
||||||
avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView)
|
avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import android.net.Uri
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
@ -96,15 +97,17 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
|
|
||||||
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
||||||
val size = processSize(data, mode)
|
val size = processSize(data, mode)
|
||||||
imageView.layoutParams.width = size.width
|
imageView.updateLayoutParams {
|
||||||
imageView.layoutParams.height = size.height
|
width = size.width
|
||||||
|
height = size.height
|
||||||
|
}
|
||||||
// a11y
|
// a11y
|
||||||
imageView.contentDescription = data.filename
|
imageView.contentDescription = data.filename
|
||||||
|
|
||||||
createGlideRequest(data, mode, imageView, size)
|
createGlideRequest(data, mode, imageView, size)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.transform(RoundedCorners(dimensionConverter.dpToPx(8)))
|
.transform(RoundedCorners(dimensionConverter.dpToPx(8)))
|
||||||
.thumbnail(0.3f)
|
// .thumbnail(0.3f)
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +120,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by Attachment Viewer
|
||||||
|
*/
|
||||||
fun render(data: Data, contextView: View, target: CustomViewTarget<*, Drawable>) {
|
fun render(data: Data, contextView: View, target: CustomViewTarget<*, Drawable>) {
|
||||||
val req = if (data.elementToDecrypt != null) {
|
val req = if (data.elementToDecrypt != null) {
|
||||||
// Encrypted image
|
// Encrypted image
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
|
|||||||
private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
|
private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
|
||||||
val content = event.content?.toModel<RoomMemberContent>() ?: return null
|
val content = event.content?.toModel<RoomMemberContent>() ?: return null
|
||||||
val roomId = event.roomId ?: return null
|
val roomId = event.roomId ?: return null
|
||||||
val dName = event.senderId?.let { session.getUser(it)?.displayName }
|
val dName = event.senderId?.let { session.getRoomMember(it, roomId)?.displayName }
|
||||||
if (Membership.INVITE == content.membership) {
|
if (Membership.INVITE == content.membership) {
|
||||||
val body = noticeEventFormatter.format(event, dName, session.getRoomSummary(roomId))
|
val body = noticeEventFormatter.format(event, dName, session.getRoomSummary(roomId))
|
||||||
?: stringProvider.getString(R.string.notification_new_invitation)
|
?: stringProvider.getString(R.string.notification_new_invitation)
|
||||||
|
@ -120,7 +120,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
|
|||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
session.getUser(session.myUserId)?.displayName
|
session.getRoomMember(session.myUserId, room.roomId)?.displayName
|
||||||
?: context?.getString(R.string.notification_sender_me),
|
?: context?.getString(R.string.notification_sender_me),
|
||||||
session.myUserId,
|
session.myUserId,
|
||||||
message,
|
message,
|
||||||
|
@ -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() {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user