Merge branch 'develop' into feature/bma/fix_cancel

This commit is contained in:
Benoit Marty 2020-12-07 12:41:37 +01:00 committed by GitHub
commit 5237eb0638
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
154 changed files with 3636 additions and 758 deletions

View File

@ -31,6 +31,7 @@
<w>ssss</w> <w>ssss</w>
<w>sygnal</w> <w>sygnal</w>
<w>threepid</w> <w>threepid</w>
<w>unpublish</w>
<w>unwedging</w> <w>unwedging</w>
</words> </words>
</dictionary> </dictionary>

View File

@ -2,13 +2,17 @@ Changes in Element 1.0.12 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
- - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428)
- Room setting: update join rules and guest access (#2442)
Improvements 🙌: Improvements 🙌:
- - Add Setting Item to Change PIN (#2462)
- Improve room history visibility setting UX (#1579)
Bugfix 🐛: Bugfix 🐛:
- Fix cancellation of sending event (#2438) - Fix cancellation of sending event (#2438)
- Double bottomsheet effect after verify with passphrase
- EditText cursor jumps to the start while typing fast (#2469)
Translations 🗣: Translations 🗣:
- -
@ -17,13 +21,14 @@ SDK API changes ⚠️:
- -
Build 🧱: Build 🧱:
- - Upgrade some dependencies and Kotlin version
- Use fragment-ktx and preference-ktx dependencies (fix lint issue KtxExtensionAvailable)
Test: Test:
- -
Other changes: Other changes:
- - Remove "Status.im" theme #2424
Changes in Element 1.0.11 (2020-11-27) Changes in Element 1.0.11 (2020-11-27)
=================================================== ===================================================

View File

@ -66,7 +66,6 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'

View File

@ -2,8 +2,8 @@
buildscript { buildscript {
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.4.10' ext.kotlin_version = '1.4.20'
ext.kotlin_coroutines_version = "1.3.9" ext.kotlin_coroutines_version = "1.4.1"
repositories { repositories {
google() google()
jcenter() jcenter()
@ -12,7 +12,7 @@ buildscript {
} }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.0' classpath 'com.android.tools.build:gradle:4.1.1'
classpath 'com.google.gms:google-services:4.3.4' classpath 'com.google.gms:google-services:4.3.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'

View File

@ -36,9 +36,9 @@ android {
dependencies { dependencies {
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
// Paging // Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.2" implementation "androidx.paging:paging-runtime-ktx:2.1.2"

View File

@ -21,34 +21,36 @@ import org.matrix.android.sdk.api.util.Cancelable
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
fun <T> singleBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Single<T> = Single.create { fun <T> singleBuilder(builder: (MatrixCallback<T>) -> Cancelable): Single<T> = Single.create { emitter ->
val callback: MatrixCallback<T> = object : MatrixCallback<T> { val callback = object : MatrixCallback<T> {
override fun onSuccess(data: T) { override fun onSuccess(data: T) {
it.onSuccess(data) // Add `!!` to fix the warning:
// "Type mismatch: type parameter with nullable bounds is used T is used where T was expected. This warning will become an error soon"
emitter.onSuccess(data!!)
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
it.tryOnError(failure) emitter.tryOnError(failure)
} }
} }
val cancelable = builder(callback) val cancelable = builder(callback)
it.setCancellable { emitter.setCancellable {
cancelable.cancel() cancelable.cancel()
} }
} }
fun <T> completableBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Completable = Completable.create { fun <T> completableBuilder(builder: (MatrixCallback<T>) -> Cancelable): Completable = Completable.create { emitter ->
val callback: MatrixCallback<T> = object : MatrixCallback<T> { val callback = object : MatrixCallback<T> {
override fun onSuccess(data: T) { override fun onSuccess(data: T) {
it.onComplete() emitter.onComplete()
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
it.tryOnError(failure) emitter.tryOnError(failure)
} }
} }
val cancelable = builder(callback) val cancelable = builder(callback)
it.setCancellable { emitter.setCancellable {
cancelable.cancel() cancelable.cancel()
} }
} }

View File

@ -35,6 +35,8 @@ import org.matrix.android.sdk.api.util.toOptional
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.Single
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
class RxRoom(private val room: Room) { class RxRoom(private val room: Room) {
@ -127,18 +129,14 @@ class RxRoom(private val room: Room) {
room.updateName(name, it) room.updateName(name, it)
} }
fun addRoomAlias(alias: String): Completable = completableBuilder<Unit> {
room.addRoomAlias(alias, it)
}
fun updateCanonicalAlias(alias: String): Completable = completableBuilder<Unit> {
room.updateCanonicalAlias(alias, it)
}
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> { fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> {
room.updateHistoryReadability(readability, it) room.updateHistoryReadability(readability, it)
} }
fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = completableBuilder<Unit> {
room.updateJoinRule(joinRules, guestAccess, it)
}
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> { fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> {
room.updateAvatar(avatarUri, fileName, it) room.updateAvatar(avatarUri, fileName, it)
} }

View File

@ -125,7 +125,6 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.2.0" implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation "androidx.core:core-ktx:1.3.2" implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
@ -146,7 +145,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:core:$markwon_version"
// Image // Image
implementation 'androidx.exifinterface:exifinterface:1.3.0' implementation 'androidx.exifinterface:exifinterface:1.3.1'
// Database // Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'

View File

@ -49,6 +49,12 @@ object EventType {
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
/**
* Note that this Event has been deprecated, see
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
* - https://github.com/matrix-org/matrix-doc/pull/2432
*/
const val STATE_ROOM_ALIASES = "m.room.aliases" const val STATE_ROOM_ALIASES = "m.room.aliases"
const val STATE_ROOM_TOMBSTONE = "m.room.tombstone" const val STATE_ROOM_TOMBSTONE = "m.room.tombstone"
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias" const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"

View File

@ -16,9 +16,6 @@
package org.matrix.android.sdk.api.session.integrationmanager package org.matrix.android.sdk.api.session.integrationmanager
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
/** /**
* This is the entry point to manage integration. You can grab an instance of this service through an active session. * This is the entry point to manage integration. You can grab an instance of this service through an active session.
*/ */
@ -80,19 +77,17 @@ interface IntegrationManagerService {
/** /**
* Offers to enable or disable the integration. * Offers to enable or disable the integration.
* @param enable the param to change * @param enable the param to change
* @param callback the matrix callback to listen for result.
* @return Cancelable * @return Cancelable
*/ */
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable suspend fun setIntegrationEnabled(enable: Boolean)
/** /**
* Offers to allow or disallow a widget. * Offers to allow or disallow a widget.
* @param stateEventId the eventId of the state event defining the widget. * @param stateEventId the eventId of the state event defining the widget.
* @param allowed the param to change * @param allowed the param to change
* @param callback the matrix callback to listen for result.
* @return Cancelable * @return Cancelable
*/ */
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean)
/** /**
* Returns true if the widget is allowed, false otherwise. * Returns true if the widget is allowed, false otherwise.
@ -105,7 +100,7 @@ interface IntegrationManagerService {
* @param widgetType the widget type to check for * @param widgetType the widget type to check for
* @param domain the domain to check for * @param domain the domain to check for
*/ */
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean)
/** /**
* Returns true if the widget domain is allowed, false otherwise. * Returns true if the widget domain is allowed, false otherwise.

View File

@ -18,6 +18,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.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.MembershipService
@ -46,6 +47,7 @@ interface Room :
DraftService, DraftService,
ReadService, ReadService,
TypingService, TypingService,
AliasService,
TagsService, TagsService,
MembershipService, MembershipService,
StateService, StateService,

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room package org.matrix.android.sdk.api.session.room
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
@ -39,4 +40,14 @@ interface RoomDirectoryService {
* Includes both the available protocols and all fields required for queries against each protocol. * Includes both the available protocols and all fields required for queries against each protocol.
*/ */
fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable fun getThirdPartyProtocol(callback: MatrixCallback<Map<String, ThirdPartyProtocol>>): Cancelable
/**
* Get the visibility of a room in the directory
*/
suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility
/**
* Set the visibility of a room in the directory
*/
suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility)
} }

View File

@ -122,6 +122,11 @@ interface RoomService {
searchOnServer: Boolean, searchOnServer: Boolean,
callback: MatrixCallback<Optional<String>>): Cancelable callback: MatrixCallback<Optional<String>>): Cancelable
/**
* Delete a room alias
*/
suspend fun deleteRoomAlias(roomAlias: String)
/** /**
* Return a live data of all local changes membership that happened since the session has been opened. * Return a live data of all local changes membership that happened since the session has been opened.
* It allows you to track this in your client to known what is currently being processed by the SDK. * It allows you to track this in your client to known what is currently being processed by the SDK.

View File

@ -0,0 +1,32 @@
/*
* 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.alias
interface AliasService {
/**
* Get list of local alias of the room
* @return the list of the aliases (full aliases, not only the local part)
*/
suspend fun getRoomAliases(): List<String>
/**
* Add local alias to the room
* @param aliasLocalPart the local part of the alias.
* Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
*/
suspend fun addAlias(aliasLocalPart: String)
}

View File

@ -0,0 +1,23 @@
/*
* 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.alias
sealed class RoomAliasError : Throwable() {
object AliasEmpty : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}

View File

@ -18,13 +18,10 @@ package org.matrix.android.sdk.api.session.room.failure
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
sealed class CreateRoomFailure : Failure.FeatureFailure() { sealed class CreateRoomFailure : Failure.FeatureFailure() {
object CreatedWithTimeout : CreateRoomFailure() object CreatedWithTimeout : CreateRoomFailure()
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
sealed class RoomAliasError : CreateRoomFailure() { data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
object AliasEmpty : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}
} }

View File

@ -21,6 +21,9 @@ import com.squareup.moshi.JsonClass
/** /**
* Class representing the EventType.STATE_ROOM_ALIASES state event content * Class representing the EventType.STATE_ROOM_ALIASES state event content
* Note that this Event has been deprecated, see
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
* - https://github.com/matrix-org/matrix-doc/pull/2432
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomAliasesContent( data class RoomAliasesContent(

View File

@ -24,5 +24,14 @@ import com.squareup.moshi.JsonClass
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RoomCanonicalAliasContent( data class RoomCanonicalAliasContent(
@Json(name = "alias") val canonicalAlias: String? = null /**
* The canonical alias for the room. If not present, null, or empty the room should be considered to have no canonical alias.
*/
@Json(name = "alias") val canonicalAlias: String? = null,
/**
* Alternative aliases the room advertises.
* This list can have aliases despite the alias field being null, empty, or otherwise not present.
*/
@Json(name = "alt_aliases") val alternativeAliases: List<String>? = null
) )

View File

@ -21,7 +21,9 @@ import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
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.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -38,21 +40,23 @@ interface StateService {
*/ */
fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
/**
* Add new alias to the room.
*/
fun addRoomAlias(roomAlias: String, callback: MatrixCallback<Unit>): Cancelable
/** /**
* Update the canonical alias of the room * Update the canonical alias of the room
* @param alias the canonical alias, or null to reset the canonical alias of this room
* @param altAliases the alternative aliases for this room. It should include the canonical alias if any.
*/ */
fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable
/** /**
* Update the history readability of the room * Update the history readability of the room
*/ */
fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable
/**
* Update the join rule and/or the guest access
*/
fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback<Unit>): Cancelable
/** /**
* Update the avatar of the room * Update the avatar of the room
*/ */

View File

@ -16,9 +16,6 @@
package org.matrix.android.sdk.api.session.room.uploads package org.matrix.android.sdk.api.session.room.uploads
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
/** /**
* This interface defines methods to get event with uploads (= attachments) sent to a room. It's implemented at the room level. * This interface defines methods to get event with uploads (= attachments) sent to a room. It's implemented at the room level.
*/ */
@ -29,7 +26,5 @@ interface UploadsService {
* @param numberOfEvents the expected number of events to retrieve. The result can contain less events. * @param numberOfEvents the expected number of events to retrieve. The result can contain less events.
* @param since token to get next page, or null to get the first page * @param since token to get next page, or null to get the first page
*/ */
fun getUploads(numberOfEvents: Int, suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult
since: String?,
callback: MatrixCallback<GetUploadsResult>): Cancelable
} }

View File

@ -16,22 +16,16 @@
package org.matrix.android.sdk.api.session.terms package org.matrix.android.sdk.api.session.terms
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable
interface TermsService { interface TermsService {
enum class ServiceType { enum class ServiceType {
IntegrationManager, IntegrationManager,
IdentityService IdentityService
} }
fun getTerms(serviceType: ServiceType, suspend fun getTerms(serviceType: ServiceType, baseUrl: String): GetTermsResponse
baseUrl: String,
callback: MatrixCallback<GetTermsResponse>): Cancelable
fun agreeToTerms(serviceType: ServiceType, suspend fun agreeToTerms(serviceType: ServiceType,
baseUrl: String, baseUrl: String,
agreedUrls: List<String>, agreedUrls: List<String>,
token: String?, token: String?)
callback: MatrixCallback<Unit>): Cancelable
} }

View File

@ -31,6 +31,7 @@ import org.matrix.android.sdk.internal.extensions.toUnsignedInt
import org.matrix.olm.OlmSAS import org.matrix.olm.OlmSAS
import org.matrix.olm.OlmUtility import org.matrix.olm.OlmUtility
import timber.log.Timber import timber.log.Timber
import java.util.Locale
/** /**
* Represents an ongoing short code interactive key verification between two devices. * Represents an ongoing short code interactive key verification between two devices.
@ -344,7 +345,7 @@ internal abstract class SASDefaultVerificationTransaction(
} }
protected fun hashUsingAgreedHashMethod(toHash: String): String? { protected fun hashUsingAgreedHashMethod(toHash: String): String? {
if ("sha256".toLowerCase() == accepted?.hash?.toLowerCase()) { if ("sha256" == accepted?.hash?.toLowerCase(Locale.ROOT)) {
val olmUtil = OlmUtility() val olmUtil = OlmUtility()
val hashBytes = olmUtil.sha256(toHash) val hashBytes = olmUtil.sha256(toHash)
olmUtil.releaseUtility() olmUtil.releaseUtility()
@ -354,12 +355,11 @@ internal abstract class SASDefaultVerificationTransaction(
} }
private fun macUsingAgreedMethod(message: String, info: String): String? { private fun macUsingAgreedMethod(message: String, info: String): String? {
if (SAS_MAC_SHA256_LONGKDF.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) { return when (accepted?.messageAuthenticationCode?.toLowerCase(Locale.ROOT)) {
return getSAS().calculateMacLongKdf(message, info) SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
} else if (SAS_MAC_SHA256.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) { SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
return getSAS().calculateMac(message, info) else -> null
} }
return null
} }
override fun getDecimalCodeRepresentation(): String { override fun getDecimalCodeRepresentation(): String {

View File

@ -0,0 +1,70 @@
/*
* 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.directory
import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.PUT
import retrofit2.http.Path
internal interface DirectoryAPI {
/**
* Get the room ID associated to the room alias.
*
* @param roomAlias the room alias.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
/**
* Get the room directory visibility.
*
* @param roomId the room id.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}")
fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): Call<RoomDirectoryVisibilityJson>
/**
* Set the room directory visibility.
*
* @param roomId the room id.
* @param body the body containing the new directory visibility
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}")
fun setRoomDirectoryVisibility(@Path("roomId") roomId: String,
@Body body: RoomDirectoryVisibilityJson): Call<Unit>
/**
* Add alias to the room.
* @param roomAlias the room alias.
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
/**
* Delete a room alias
* @param roomAlias the room alias.
*/
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun deleteRoomAlias(@Path("roomAlias") roomAlias: String): Call<Unit>
}

View File

@ -0,0 +1,29 @@
/*
* 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.directory
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
@JsonClass(generateAdapter = true)
internal data class RoomDirectoryVisibilityJson(
/**
* The visibility of the room in the directory. One of: ["private", "public"]
*/
@Json(name = "visibility") val visibility: RoomDirectoryVisibility
)

View File

@ -16,10 +16,8 @@
package org.matrix.android.sdk.internal.session.integrationmanager package org.matrix.android.sdk.internal.session.integrationmanager
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.util.Cancelable
import javax.inject.Inject import javax.inject.Inject
internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService { internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService {
@ -44,20 +42,20 @@ internal class DefaultIntegrationManagerService @Inject constructor(private val
return integrationManager.isIntegrationEnabled() return integrationManager.isIntegrationEnabled()
} }
override fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable { override suspend fun setIntegrationEnabled(enable: Boolean) {
return integrationManager.setIntegrationEnabled(enable, callback) integrationManager.setIntegrationEnabled(enable)
} }
override fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable { override suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) {
return integrationManager.setWidgetAllowed(stateEventId, allowed, callback) integrationManager.setWidgetAllowed(stateEventId, allowed)
} }
override fun isWidgetAllowed(stateEventId: String): Boolean { override fun isWidgetAllowed(stateEventId: String): Boolean {
return integrationManager.isWidgetAllowed(stateEventId) return integrationManager.isWidgetAllowed(stateEventId)
} }
override fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable { override suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) {
return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback) integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed)
} }
override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean { override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean {

View File

@ -20,15 +20,12 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LifecycleRegistry
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
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.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.extensions.observeNotNull import org.matrix.android.sdk.internal.extensions.observeNotNull
@ -41,7 +38,6 @@ import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccoun
import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory
import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence
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 timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -137,22 +133,17 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
return integrationProvisioningContent?.enabled ?: false return integrationProvisioningContent?.enabled ?: false
} }
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable { suspend fun setIntegrationEnabled(enable: Boolean) {
val isIntegrationEnabled = isIntegrationEnabled() val isIntegrationEnabled = isIntegrationEnabled()
if (enable == isIntegrationEnabled) { if (enable == isIntegrationEnabled) {
callback.onSuccess(Unit) return
return NoOpCancellable
} }
val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable) val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable)
val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent) val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent)
return updateUserAccountDataTask return updateUserAccountDataTask.execute(params)
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable { suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
val newContent = if (currentContent == null) { val newContent = if (currentContent == null) {
@ -165,11 +156,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
currentContent.copy(widgets = allowedWidgets) currentContent.copy(widgets = allowedWidgets)
} }
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent) val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
return updateUserAccountDataTask return updateUserAccountDataTask.execute(params)
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
fun isWidgetAllowed(stateEventId: String): Boolean { fun isWidgetAllowed(stateEventId: String): Boolean {
@ -178,7 +165,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
return currentContent?.widgets?.get(stateEventId) ?: false return currentContent?.widgets?.get(stateEventId) ?: false
} }
fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable { suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) {
val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS) val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS)
val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>() val currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
val newContent = if (currentContent == null) { val newContent = if (currentContent == null) {
@ -195,11 +182,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
currentContent.copy(native = nativeAllowedWidgets) currentContent.copy(native = nativeAllowedWidgets)
} }
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent) val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
return updateUserAccountDataTask return updateUserAccountDataTask.execute(params)
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean { fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean {

View File

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
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.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.alias.AliasService
import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.call.RoomCallService
import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.MembershipService
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -58,6 +59,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
private val roomCallService: RoomCallService, private val roomCallService: RoomCallService,
private val readService: ReadService, private val readService: ReadService,
private val typingService: TypingService, private val typingService: TypingService,
private val aliasService: AliasService,
private val tagsService: TagsService, private val tagsService: TagsService,
private val cryptoService: CryptoService, private val cryptoService: CryptoService,
private val relationService: RelationService, private val relationService: RelationService,
@ -76,6 +78,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
RoomCallService by roomCallService, RoomCallService by roomCallService,
ReadService by readService, ReadService by readService,
TypingService by typingService, TypingService by typingService,
AliasService by aliasService,
TagsService by tagsService, TagsService by tagsService,
RelationService by relationService, RelationService by relationService,
MembershipService by roomMembersService, MembershipService by roomMembersService,

View File

@ -18,18 +18,24 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
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 javax.inject.Inject import javax.inject.Inject
internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask, internal class DefaultRoomDirectoryService @Inject constructor(
private val getPublicRoomTask: GetPublicRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask, private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService { private val taskExecutor: TaskExecutor) : RoomDirectoryService {
override fun getPublicRooms(server: String?, override fun getPublicRooms(server: String?,
@ -49,4 +55,12 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
} }
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility {
return getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))
}
override suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) {
setRoomDirectoryVisibilityTask.execute(SetRoomDirectoryVisibilityTask.Params(roomId, roomDirectoryVisibility))
}
} }

View File

@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
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
@ -53,6 +54,7 @@ internal class DefaultRoomService @Inject constructor(
private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask,
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
private val roomIdByAliasTask: GetRoomIdByAliasTask, private val roomIdByAliasTask: GetRoomIdByAliasTask,
private val deleteRoomAliasTask: DeleteRoomAliasTask,
private val roomGetter: RoomGetter, private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource, private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@ -125,6 +127,10 @@ internal class DefaultRoomService @Inject constructor(
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override suspend fun deleteRoomAlias(roomAlias: String) {
deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
}
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> { override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
return roomChangeMembershipStateDataSource.getLiveStates() return roomChangeMembershipStateDataSource.getLiveStates()
} }

View File

@ -23,8 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsRe
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody import org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody
import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse
import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse
@ -321,20 +320,11 @@ internal interface RoomAPI {
@Body body: ReportContentBody): Call<Unit> @Body body: ReportContentBody): Call<Unit>
/** /**
* Get the room ID associated to the room alias. * Get a list of aliases maintained by the local server for the given room.
* * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases
* @param roomAlias the room alias.
*/ */
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases")
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription> fun getAliases(@Path("roomId") roomId: String): Call<GetAliasesResponse>
/**
* Add alias to the room.
* @param roomAlias the room alias.
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun addRoomAlias(@Path("roomAlias") roomAlias: String,
@Body body: AddRoomAliasBody): Call<Unit>
/** /**
* Inform that the user is starting to type or has stopped typing * Inform that the user is starting to type or has stopped typing

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
@ -54,6 +55,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val roomCallServiceFactory: DefaultRoomCallService.Factory, private val roomCallServiceFactory: DefaultRoomCallService.Factory,
private val readServiceFactory: DefaultReadService.Factory, private val readServiceFactory: DefaultReadService.Factory,
private val typingServiceFactory: DefaultTypingService.Factory, private val typingServiceFactory: DefaultTypingService.Factory,
private val aliasServiceFactory: DefaultAliasService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory, private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory, private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory,
@ -76,6 +78,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomCallService = roomCallServiceFactory.create(roomId), roomCallService = roomCallServiceFactory.create(roomId),
readService = readServiceFactory.create(roomId), readService = readServiceFactory.create(roomId),
typingService = typingServiceFactory.create(roomId), typingService = typingServiceFactory.create(roomId),
aliasService = aliasServiceFactory.create(roomId),
tagsService = tagsServiceFactory.create(roomId), tagsService = tagsServiceFactory.create(roomId),
cryptoService = cryptoService, cryptoService = cryptoService,
relationService = relationServiceFactory.create(roomId), relationService = relationServiceFactory.create(roomId),

View File

@ -26,16 +26,25 @@ import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.internal.session.DefaultFileService import org.matrix.android.sdk.internal.session.DefaultFileService
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAliasesTask
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
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.alias.GetRoomLocalAliasesTask
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.create.DefaultCreateRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.membership.admin.DefaultMembershipAdminTask import org.matrix.android.sdk.internal.session.room.membership.admin.DefaultMembershipAdminTask
@ -90,6 +99,13 @@ internal abstract class RoomModule {
return retrofit.create(RoomAPI::class.java) return retrofit.create(RoomAPI::class.java)
} }
@Provides
@JvmStatic
@SessionScope
fun providesDirectoryAPI(retrofit: Retrofit): DirectoryAPI {
return retrofit.create(DirectoryAPI::class.java)
}
@Provides @Provides
@JvmStatic @JvmStatic
fun providesParser(): Parser { fun providesParser(): Parser {
@ -127,6 +143,12 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask
@Binds
abstract fun bindGetRoomDirectoryVisibilityTask(task: DefaultGetRoomDirectoryVisibilityTask): GetRoomDirectoryVisibilityTask
@Binds
abstract fun bindSetRoomDirectoryVisibilityTask(task: DefaultSetRoomDirectoryVisibilityTask): SetRoomDirectoryVisibilityTask
@Binds @Binds
abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
@ -181,9 +203,15 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
@Binds
abstract fun bindGetRoomLocalAliasesTask(task: DefaultGetRoomLocalAliasesTask): GetRoomLocalAliasesTask
@Binds @Binds
abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask
@Binds
abstract fun bindDeleteRoomAliasTask(task: DefaultDeleteRoomAliasTask): DeleteRoomAliasTask
@Binds @Binds
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask

View File

@ -16,28 +16,38 @@
package org.matrix.android.sdk.internal.session.room.alias package org.matrix.android.sdk.internal.session.room.alias
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker.Companion.toFullLocalAlias
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> { internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> {
data class Params( data class Params(
val roomId: String, val roomId: String,
val roomAlias: String /**
* the local part of the alias.
* Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
*/
val aliasLocalPart: String
) )
} }
internal class DefaultAddRoomAliasTask @Inject constructor( internal class DefaultAddRoomAliasTask @Inject constructor(
private val roomAPI: RoomAPI, @UserId private val userId: String,
private val directoryAPI: DirectoryAPI,
private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
private val eventBus: EventBus private val eventBus: EventBus
) : AddRoomAliasTask { ) : AddRoomAliasTask {
override suspend fun execute(params: AddRoomAliasTask.Params) { override suspend fun execute(params: AddRoomAliasTask.Params) {
aliasAvailabilityChecker.check(params.aliasLocalPart)
executeRequest<Unit>(eventBus) { executeRequest<Unit>(eventBus) {
apiCall = roomAPI.addRoomAlias( apiCall = directoryAPI.addRoomAlias(
roomAlias = params.roomAlias, roomAlias = params.aliasLocalPart.toFullLocalAlias(userId),
body = AddRoomAliasBody( body = AddRoomAliasBody(
roomId = params.roomId roomId = params.roomId
) )

View File

@ -0,0 +1,41 @@
/*
* 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.room.alias
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import org.matrix.android.sdk.api.session.room.alias.AliasService
internal class DefaultAliasService @AssistedInject constructor(
@Assisted private val roomId: String,
private val getRoomLocalAliasesTask: GetRoomLocalAliasesTask,
private val addRoomAliasTask: AddRoomAliasTask
) : AliasService {
@AssistedInject.Factory
interface Factory {
fun create(roomId: String): AliasService
}
override suspend fun getRoomAliases(): List<String> {
return getRoomLocalAliasesTask.execute(GetRoomLocalAliasesTask.Params(roomId))
}
override suspend fun addAlias(aliasLocalPart: String) {
addRoomAliasTask.execute(AddRoomAliasTask.Params(roomId, aliasLocalPart))
}
}

View File

@ -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.room.alias
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface DeleteRoomAliasTask : Task<DeleteRoomAliasTask.Params, Unit> {
data class Params(
val roomAlias: String
)
}
internal class DefaultDeleteRoomAliasTask @Inject constructor(
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : DeleteRoomAliasTask {
override suspend fun execute(params: DeleteRoomAliasTask.Params) {
executeRequest<Unit>(eventBus) {
apiCall = directoryAPI.deleteRoomAlias(
roomAlias = params.roomAlias
)
}
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.room.alias
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class GetAliasesResponse(
/**
* Required. The server's local aliases on the room. Can be empty.
*/
@Json(name = "aliases") val aliases: List<String> = emptyList()
)

View File

@ -25,7 +25,7 @@ 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
import org.matrix.android.sdk.internal.di.SessionDatabase 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.directory.DirectoryAPI
import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -38,7 +38,7 @@ internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Opti
internal class DefaultGetRoomIdByAliasTask @Inject constructor( internal class DefaultGetRoomIdByAliasTask @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val roomAPI: RoomAPI, private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus private val eventBus: EventBus
) : GetRoomIdByAliasTask { ) : GetRoomIdByAliasTask {
@ -53,7 +53,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
} else { } else {
roomId = tryOrNull("## Failed to get roomId from alias") { roomId = tryOrNull("## Failed to get roomId from alias") {
executeRequest<RoomAliasDescription>(eventBus) { executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
} }
}?.roomId }?.roomId
Optional.from(roomId) Optional.from(roomId)

View File

@ -0,0 +1,44 @@
/*
* 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.room.alias
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetRoomLocalAliasesTask : Task<GetRoomLocalAliasesTask.Params, List<String>> {
data class Params(
val roomId: String
)
}
internal class DefaultGetRoomLocalAliasesTask @Inject constructor(
private val roomAPI: RoomAPI,
private val eventBus: EventBus
) : GetRoomLocalAliasesTask {
override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List<String> {
// We do not check for "org.matrix.msc2432", so the API may be missing
val response = executeRequest<GetAliasesResponse>(eventBus) {
apiCall = roomAPI.getAliases(roomId = params.roomId)
}
return response.aliases
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.room.alias
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import javax.inject.Inject
internal class RoomAliasAvailabilityChecker @Inject constructor(
@UserId private val userId: String,
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) {
/**
* @param aliasLocalPart the local part of the alias.
* Ex: for the alias "#my_alias:example.org", the local part is "my_alias"
*/
@Throws(RoomAliasError::class)
suspend fun check(aliasLocalPart: String?) {
if (aliasLocalPart.isNullOrEmpty()) {
throw RoomAliasError.AliasEmpty
}
// Check alias availability
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
try {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = directoryAPI.getRoomIdByAlias(fullAlias)
}
} catch (throwable: Throwable) {
if (throwable is Failure.ServerError && throwable.httpCode == 404) {
// This is a 404, so the alias is available: nominal case
null
} else {
// Other error, propagate it
throw throwable
}
}
?.let {
// Alias already exists: error case
throw RoomAliasError.AliasNotAvailable
}
}
companion object {
internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":")
}
}

View File

@ -22,6 +22,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
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.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
@ -31,10 +32,9 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields
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.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.di.UserId
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.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
@ -47,8 +47,8 @@ internal interface CreateRoomTask : Task<CreateRoomParams, String>
internal class DefaultCreateRoomTask @Inject constructor( internal class DefaultCreateRoomTask @Inject constructor(
private val roomAPI: RoomAPI, private val roomAPI: RoomAPI,
@UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
private val directChatsHelper: DirectChatsHelper, private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val readMarkersTask: SetReadMarkersTask, private val readMarkersTask: SetReadMarkersTask,
@ -65,27 +65,10 @@ internal class DefaultCreateRoomTask @Inject constructor(
} else null } else null
if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) {
if (params.roomAliasName.isNullOrEmpty()) {
throw CreateRoomFailure.RoomAliasError.AliasEmpty
}
// Check alias availability
val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":")
try { try {
executeRequest<RoomAliasDescription>(eventBus) { aliasAvailabilityChecker.check(params.roomAliasName)
apiCall = roomAPI.getRoomIdByAlias(fullAlias) } catch (aliasError: RoomAliasError) {
} throw CreateRoomFailure.AliasError(aliasError)
} catch (throwable: Throwable) {
if (throwable is Failure.ServerError && throwable.httpCode == 404) {
// This is a 404, so the alias is available: nominal case
null
} else {
// Other error, propagate it
throw throwable
}
}
?.let {
// Alias already exists: error case
throw CreateRoomFailure.RoomAliasError.AliasNotAvailable
} }
} }
@ -104,7 +87,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
} else if (throwable.httpCode == 400 } else if (throwable.httpCode == 400
&& throwable.error.code == MatrixError.M_UNKNOWN && throwable.error.code == MatrixError.M_UNKNOWN
&& throwable.error.message == "Invalid characters in room alias") { && throwable.error.message == "Invalid characters in room alias") {
throw CreateRoomFailure.RoomAliasError.AliasInvalid throw CreateRoomFailure.AliasError(RoomAliasError.AliasInvalid)
} }
} }
throw throwable throw throwable

View File

@ -0,0 +1,44 @@
/*
* 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.room.directory
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetRoomDirectoryVisibilityTask : Task<GetRoomDirectoryVisibilityTask.Params, RoomDirectoryVisibility> {
data class Params(
val roomId: String
)
}
internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor(
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : GetRoomDirectoryVisibilityTask {
override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility {
return executeRequest<RoomDirectoryVisibilityJson>(eventBus) {
apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId)
}
.visibility
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.room.directory
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface SetRoomDirectoryVisibilityTask : Task<SetRoomDirectoryVisibilityTask.Params, Unit> {
data class Params(
val roomId: String,
val roomDirectoryVisibility: RoomDirectoryVisibility
)
}
internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor(
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : SetRoomDirectoryVisibilityTask {
override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) {
executeRequest<Unit>(eventBus) {
apiCall = directoryAPI.setRoomDirectoryVisibility(
params.roomId,
RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility)
)
}
}
}

View File

@ -21,7 +21,6 @@ import org.matrix.android.sdk.R
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.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
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.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@ -71,12 +70,6 @@ internal class RoomDisplayNameResolver @Inject constructor(
return name return name
} }
val aliases = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
name = ContentMapper.map(aliases?.content).toModel<RoomAliasesContent>()?.aliases?.firstOrNull()
if (!name.isNullOrEmpty()) {
return name
}
val roomMembers = RoomMemberHelper(realm, roomId) val roomMembers = RoomMemberHelper(realm, roomId)
val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll() val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll()

View File

@ -24,7 +24,13 @@ import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
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
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
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.state.StateService import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
@ -104,18 +110,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
) )
} }
override fun addRoomAlias(roomAlias: String, callback: MatrixCallback<Unit>): Cancelable { override fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable {
return addRoomAliasTask
.configureWith(AddRoomAliasTask.Params(roomId, roomAlias)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable {
return sendStateEvent( return sendStateEvent(
eventType = EventType.STATE_ROOM_CANONICAL_ALIAS, eventType = EventType.STATE_ROOM_CANONICAL_ALIAS,
body = mapOf("alias" to alias), body = RoomCanonicalAliasContent(
canonicalAlias = alias,
alternativeAliases = altAliases
// Ensure there is no duplicate
.distinct()
// Ensure the canonical alias is not also included in the alt alias
.minus(listOfNotNull(alias))
// Sort for the cleanup
.sorted()
).toContent(),
callback = callback, callback = callback,
stateKey = null stateKey = null
) )
@ -130,6 +137,31 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
) )
} }
override fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
if (joinRules != null) {
awaitCallback<Unit> {
sendStateEvent(
eventType = EventType.STATE_ROOM_JOIN_RULES,
body = RoomJoinRulesContent(joinRules).toContent(),
callback = it,
stateKey = null
)
}
}
if (guestAccess != null) {
awaitCallback<Unit> {
sendStateEvent(
eventType = EventType.STATE_ROOM_GUEST_ACCESS,
body = RoomGuestAccessContent(guestAccess).toContent(),
callback = it,
stateKey = null
)
}
}
}
}
override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable { override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg")

View File

@ -18,17 +18,12 @@ package org.matrix.android.sdk.internal.session.room.uploads
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.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.room.uploads.GetUploadsResult import org.matrix.android.sdk.api.session.room.uploads.GetUploadsResult
import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.room.uploads.UploadsService
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 DefaultUploadsService @AssistedInject constructor( internal class DefaultUploadsService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
private val taskExecutor: TaskExecutor,
private val getUploadsTask: GetUploadsTask, private val getUploadsTask: GetUploadsTask,
private val cryptoService: CryptoService private val cryptoService: CryptoService
) : UploadsService { ) : UploadsService {
@ -38,11 +33,7 @@ internal class DefaultUploadsService @AssistedInject constructor(
fun create(roomId: String): UploadsService fun create(roomId: String): UploadsService
} }
override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback<GetUploadsResult>): Cancelable { override suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult {
return getUploadsTask return getUploadsTask.execute(GetUploadsTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), numberOfEvents, since))
.configureWith(GetUploadsTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), numberOfEvents, since)) {
this.callback = callback
}
.executeBy(taskExecutor)
} }
} }

View File

@ -17,11 +17,10 @@
package org.matrix.android.sdk.internal.session.terms package org.matrix.android.sdk.internal.session.terms
import dagger.Lazy import dagger.Lazy
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.withContext
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.terms.GetTermsResponse import org.matrix.android.sdk.api.session.terms.GetTermsResponse
import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.RetrofitFactory
@ -33,8 +32,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTe
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
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
import org.matrix.android.sdk.internal.util.ensureTrailingSlash import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -49,13 +46,11 @@ internal class DefaultTermsService @Inject constructor(
private val getOpenIdTokenTask: GetOpenIdTokenTask, private val getOpenIdTokenTask: GetOpenIdTokenTask,
private val identityRegisterTask: IdentityRegisterTask, private val identityRegisterTask: IdentityRegisterTask,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers
private val taskExecutor: TaskExecutor
) : TermsService { ) : TermsService {
override fun getTerms(serviceType: TermsService.ServiceType, override suspend fun getTerms(serviceType: TermsService.ServiceType,
baseUrl: String, baseUrl: String): GetTermsResponse {
callback: MatrixCallback<GetTermsResponse>): Cancelable { return withContext(coroutineDispatchers.main) {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
val url = buildUrl(baseUrl, serviceType) val url = buildUrl(baseUrl, serviceType)
val termsResponse = executeRequest<TermsResponse>(null) { val termsResponse = executeRequest<TermsResponse>(null) {
apiCall = termsAPI.getTerms("${url}terms") apiCall = termsAPI.getTerms("${url}terms")
@ -64,12 +59,11 @@ internal class DefaultTermsService @Inject constructor(
} }
} }
override fun agreeToTerms(serviceType: TermsService.ServiceType, override suspend fun agreeToTerms(serviceType: TermsService.ServiceType,
baseUrl: String, baseUrl: String,
agreedUrls: List<String>, agreedUrls: List<String>,
token: String?, token: String?) {
callback: MatrixCallback<Unit>): Cancelable { withContext(coroutineDispatchers.main) {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
val url = buildUrl(baseUrl, serviceType) val url = buildUrl(baseUrl, serviceType)
val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl)

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.util package org.matrix.android.sdk.internal.util
import java.security.MessageDigest import java.security.MessageDigest
import java.util.Locale
/** /**
* Compute a Hash of a String, using md5 algorithm * Compute a Hash of a String, using md5 algorithm
@ -26,7 +27,7 @@ fun String.md5() = try {
digest.update(toByteArray()) digest.update(toByteArray())
digest.digest() digest.digest()
.joinToString("") { String.format("%02X", it) } .joinToString("") { String.format("%02X", it) }
.toLowerCase() .toLowerCase(Locale.ROOT)
} catch (exc: Exception) { } catch (exc: Exception) {
// Should not happen, but just in case // Should not happen, but just in case
hashCode().toString() hashCode().toString()

View File

@ -246,7 +246,7 @@
<plurals name="notice_room_aliases_removed"> <plurals name="notice_room_aliases_removed">
<item quantity="one">%1$s removed %2$s as an address for this room.</item> <item quantity="one">%1$s removed %2$s as an address for this room.</item>
<item quantity="other">%1$s removed %3$s as addresses for this room.</item> <item quantity="other">%1$s removed %2$s as addresses for this room.</item>
</plurals> </plurals>
<plurals name="notice_room_aliases_removed_by_you"> <plurals name="notice_room_aliases_removed_by_you">
@ -262,6 +262,33 @@
<string name="notice_room_canonical_alias_unset">"%1$s removed the main address for this room."</string> <string name="notice_room_canonical_alias_unset">"%1$s removed the main address for this room."</string>
<string name="notice_room_canonical_alias_unset_by_you">"You removed the main address for this room."</string> <string name="notice_room_canonical_alias_unset_by_you">"You removed the main address for this room."</string>
<plurals name="notice_room_canonical_alias_alternative_added">
<item quantity="one">%1$s added the alternative address %2$s for this room.</item>
<item quantity="other">%1$s added the alternative addresses %2$s for this room.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_added_by_you">
<item quantity="one">You added the alternative address %1$s for this room.</item>
<item quantity="other">You added the alternative addresses %1$s for this room.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_removed">
<item quantity="one">%1$s removed the alternative address %2$s for this room.</item>
<item quantity="other">%1$s removed the alternative addresses %2$s for this room.</item>
</plurals>
<plurals name="notice_room_canonical_alias_alternative_removed_by_you">
<item quantity="one">You removed the alternative address %1$s for this room.</item>
<item quantity="other">You removed the alternative addresses %1$s for this room.</item>
</plurals>
<string name="notice_room_canonical_alias_alternative_changed">%1$s changed the alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_alternative_changed_by_you">You changed the alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_main_and_alternative_changed">%1$s changed the main and alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_main_and_alternative_changed_by_you">You changed the main and alternative addresses for this room.</string>
<string name="notice_room_canonical_alias_no_change">%1$s changed the addresses for this room.</string>
<string name="notice_room_canonical_alias_no_change_by_you">You changed the addresses for this room.</string>
<string name="notice_room_guest_access_can_join">"%1$s has allowed guests to join the room."</string> <string name="notice_room_guest_access_can_join">"%1$s has allowed guests to join the room."</string>
<string name="notice_room_guest_access_can_join_by_you">"You have allowed guests to join the room."</string> <string name="notice_room_guest_access_can_join_by_you">"You have allowed guests to join the room."</string>
<string name="notice_direct_room_guest_access_can_join">"%1$s has allowed guests to join here."</string> <string name="notice_direct_room_guest_access_can_join">"%1$s has allowed guests to join here."</string>

View File

@ -43,8 +43,8 @@ android {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01" implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
implementation 'androidx.exifinterface:exifinterface:1.3.0' implementation 'androidx.exifinterface:exifinterface:1.3.1'
// Log // Log
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'

View File

@ -315,9 +315,8 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06" implementation "androidx.recyclerview:recyclerview:1.2.0-alpha06"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.2' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.0.0" implementation "androidx.sharetarget:sharetarget:1.0.0"
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
@ -362,11 +361,11 @@ dependencies {
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
// Pref // Pref
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
// UI // UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.material:material:1.3.0-alpha04'
implementation 'me.gujun.android:span:1.7' implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version" implementation "io.noties.markwon:html:$markwon_version"
@ -374,7 +373,7 @@ dependencies {
implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1' implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version" implementation "androidx.autofill:autofill:$autofill_version"
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta10' implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta12'
// Custom Tab // Custom Tab
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
@ -418,7 +417,7 @@ dependencies {
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0' kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
// gplay flavor only // gplay flavor only
gplayImplementation('com.google.firebase:firebase-messaging:20.3.0') { gplayImplementation('com.google.firebase:firebase-messaging:21.0.0') {
exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'

View File

@ -41,6 +41,7 @@
<issue id="ObsoleteSdkInt" severity="error" /> <issue id="ObsoleteSdkInt" severity="error" />
<issue id="Recycle" severity="error" /> <issue id="Recycle" severity="error" />
<issue id="KotlinPropertyAccess" severity="error" /> <issue id="KotlinPropertyAccess" severity="error" />
<issue id="DefaultLocale" severity="error" />
<issue id="InvalidPackage"> <issue id="InvalidPackage">
<!-- Ignore error from HtmlCompressor lib --> <!-- Ignore error from HtmlCompressor lib -->
@ -52,6 +53,9 @@
<!-- Manifest --> <!-- Manifest -->
<issue id="PermissionImpliesUnsupportedChromeOsHardware" severity="error" /> <issue id="PermissionImpliesUnsupportedChromeOsHardware" severity="error" />
<!-- Dependencies -->
<issue id="KtxExtensionAvailable" severity="error" />
<!-- Timber --> <!-- Timber -->
<!-- This rule is failing on CI because it's marked as unknwown rule id :/--> <!-- This rule is failing on CI because it's marked as unknwown rule id :/-->
<!-- <issue id="BinaryOperationInTimber" severity="error" />--> <!-- <issue id="BinaryOperationInTimber" severity="error" />-->

View File

@ -4,7 +4,7 @@
android:id="@+id/test_linkify_coordinator" android:id="@+id/test_linkify_coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/riot_secondary_text_color_status" android:background="#7F70808D"
tools:context=".features.debug.TestLinkifyActivity"> tools:context=".features.debug.TestLinkifyActivity">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView

View File

@ -41,14 +41,4 @@
</FrameLayout> </FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:theme="@style/AppTheme.Status">
<include layout="@layout/demo_theme_sample" />
</FrameLayout>
</LinearLayout> </LinearLayout>

View File

@ -18,7 +18,7 @@ package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent import android.content.Intent
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.messaging.FirebaseMessaging
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.core.utils.startAddGoogleAccountIntent import im.vector.app.core.utils.startAddGoogleAccountIntent
@ -36,29 +36,33 @@ class TestFirebaseToken @Inject constructor(private val context: AppCompatActivi
override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) { override fun perform(activityResultLauncher: ActivityResultLauncher<Intent>) {
status = TestStatus.RUNNING status = TestStatus.RUNNING
try { try {
FirebaseInstanceId.getInstance().instanceId FirebaseMessaging.getInstance().token
.addOnCompleteListener(context) { task -> .addOnCompleteListener(context) { task ->
if (!task.isSuccessful) { if (!task.isSuccessful) {
val errorMsg = if (task.exception == null) "Unknown" else task.exception!!.localizedMessage
// Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated) // Can't find where this constant is (not documented -or deprecated in docs- and all obfuscated)
if ("SERVICE_NOT_AVAILABLE".equals(errorMsg)) { description = when (val errorMsg = task.exception?.localizedMessage ?: "Unknown") {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg) "SERVICE_NOT_AVAILABLE" -> {
} else if ("TOO_MANY_REGISTRATIONS".equals(errorMsg)) { stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg)
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg) }
} else if ("ACCOUNT_MISSING".equals(errorMsg)) { "TOO_MANY_REGISTRATIONS" -> {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg) stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg)
}
"ACCOUNT_MISSING" -> {
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) { quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() { override fun doFix() {
startAddGoogleAccountIntent(context, activityResultLauncher) startAddGoogleAccountIntent(context, activityResultLauncher)
} }
} }
} else { stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg) }
else -> {
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg)
}
} }
status = TestStatus.FAILED status = TestStatus.FAILED
} else { } else {
task.result?.token?.let { token -> task.result?.let { token ->
val tok = token.substring(0, Math.min(8, token.length)) + "********************" val tok = token.take(8) + "********************"
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok) description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok)
Timber.e("Retrieved FCM token success [$tok].") Timber.e("Retrieved FCM token success [$tok].")
// Ensure it is well store in our local storage // Ensure it is well store in our local storage

View File

@ -21,7 +21,7 @@ import android.widget.Toast
import androidx.core.content.edit import androidx.core.content.edit
import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.messaging.FirebaseMessaging
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.DefaultSharedPreferences import im.vector.app.core.di.DefaultSharedPreferences
@ -71,14 +71,16 @@ object FcmHelper {
// 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features'
if (checkPlayServices(activity)) { if (checkPlayServices(activity)) {
try { try {
FirebaseInstanceId.getInstance().instanceId FirebaseMessaging.getInstance().token
.addOnSuccessListener(activity) { instanceIdResult -> .addOnSuccessListener { token ->
storeFcmToken(activity, instanceIdResult.token) storeFcmToken(activity, token)
if (registerPusher) { if (registerPusher) {
pushersManager.registerPusherWithFcmKey(instanceIdResult.token) pushersManager.registerPusherWithFcmKey(token)
} }
} }
.addOnFailureListener(activity) { e -> Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") } .addOnFailureListener { e ->
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed")
}
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed") Timber.e(e, "## ensureFcmTokenIsRetrieved() : failed")
} }

View File

@ -36,6 +36,7 @@ import im.vector.app.features.crypto.recover.BootstrapMigrateBackupFragment
import im.vector.app.features.crypto.recover.BootstrapSaveRecoveryKeyFragment import im.vector.app.features.crypto.recover.BootstrapSaveRecoveryKeyFragment
import im.vector.app.features.crypto.recover.BootstrapSetupRecoveryKeyFragment import im.vector.app.features.crypto.recover.BootstrapSetupRecoveryKeyFragment
import im.vector.app.features.crypto.recover.BootstrapWaitingFragment import im.vector.app.features.crypto.recover.BootstrapWaitingFragment
import im.vector.app.features.crypto.verification.QuadSLoadingFragment
import im.vector.app.features.crypto.verification.cancel.VerificationCancelFragment import im.vector.app.features.crypto.verification.cancel.VerificationCancelFragment
import im.vector.app.features.crypto.verification.cancel.VerificationNotMeFragment import im.vector.app.features.crypto.verification.cancel.VerificationNotMeFragment
import im.vector.app.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.app.features.crypto.verification.choose.VerificationChooseMethodFragment
@ -83,6 +84,7 @@ import im.vector.app.features.roomprofile.RoomProfileFragment
import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
@ -363,6 +365,11 @@ interface FragmentModule {
@FragmentKey(RoomSettingsFragment::class) @FragmentKey(RoomSettingsFragment::class)
fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomAliasFragment::class)
fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(RoomMemberProfileFragment::class) @FragmentKey(RoomMemberProfileFragment::class)
@ -418,6 +425,11 @@ interface FragmentModule {
@FragmentKey(VerificationCancelFragment::class) @FragmentKey(VerificationCancelFragment::class)
fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment
@Binds
@IntoMap
@FragmentKey(QuadSLoadingFragment::class)
fun bindQuadSLoadingFragment(fragment: QuadSLoadingFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(VerificationNotMeFragment::class) @FragmentKey(VerificationNotMeFragment::class)

View File

@ -67,6 +67,9 @@ import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity
import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity
import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet
import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.RoomProfileActivity
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet
import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet
import im.vector.app.features.share.IncomingShareActivity import im.vector.app.features.share.IncomingShareActivity
@ -153,6 +156,9 @@ interface ScreenComponent {
fun inject(bottomSheet: ViewEditHistoryBottomSheet) fun inject(bottomSheet: ViewEditHistoryBottomSheet)
fun inject(bottomSheet: DisplayReadReceiptsBottomSheet) fun inject(bottomSheet: DisplayReadReceiptsBottomSheet)
fun inject(bottomSheet: RoomListQuickActionsBottomSheet) fun inject(bottomSheet: RoomListQuickActionsBottomSheet)
fun inject(bottomSheet: RoomAliasBottomSheet)
fun inject(bottomSheet: RoomHistoryVisibilityBottomSheet)
fun inject(bottomSheet: RoomJoinRuleBottomSheet)
fun inject(bottomSheet: VerificationBottomSheet) fun inject(bottomSheet: VerificationBottomSheet)
fun inject(bottomSheet: DeviceVerificationInfoBottomSheet) fun inject(bottomSheet: DeviceVerificationInfoBottomSheet)
fun inject(bottomSheet: DeviceListBottomSheet) fun inject(bottomSheet: DeviceListBottomSheet)

View File

@ -35,6 +35,9 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.reactions.EmojiChooserViewModel import im.vector.app.features.reactions.EmojiChooserViewModel
import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel
import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel
import im.vector.app.features.userdirectory.UserListSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel
@Module @Module
@ -105,6 +108,21 @@ interface ViewModelModule {
@ViewModelKey(RoomListQuickActionsSharedActionViewModel::class) @ViewModelKey(RoomListQuickActionsSharedActionViewModel::class)
fun bindRoomListQuickActionsSharedActionViewModel(viewModel: RoomListQuickActionsSharedActionViewModel): ViewModel fun bindRoomListQuickActionsSharedActionViewModel(viewModel: RoomListQuickActionsSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomAliasBottomSheetSharedActionViewModel::class)
fun bindRoomAliasBottomSheetSharedActionViewModel(viewModel: RoomAliasBottomSheetSharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomHistoryVisibilitySharedActionViewModel::class)
fun bindRoomHistoryVisibilitySharedActionViewModel(viewModel: RoomHistoryVisibilitySharedActionViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RoomJoinRuleSharedActionViewModel::class)
fun bindRoomJoinRuleSharedActionViewModel(viewModel: RoomJoinRuleSharedActionViewModel): ViewModel
@Binds @Binds
@IntoMap @IntoMap
@ViewModelKey(RoomDirectorySharedActionViewModel::class) @ViewModelKey(RoomDirectorySharedActionViewModel::class)

View File

@ -21,6 +21,7 @@ import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
@ -43,6 +44,13 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
@DrawableRes @DrawableRes
var iconRes: Int = 0 var iconRes: Int = 0
@EpoxyAttribute
var showIcon = true
@EpoxyAttribute
var text: String? = null
@StringRes
@EpoxyAttribute @EpoxyAttribute
var textRes: Int = 0 var textRes: Int = 0
@ -75,9 +83,14 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
} else { } else {
ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary)
} }
holder.icon.isVisible = showIcon
holder.icon.setImageResource(iconRes) holder.icon.setImageResource(iconRes)
ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor)) ImageViewCompat.setImageTintList(holder.icon, ColorStateList.valueOf(tintColor))
if (text != null) {
holder.text.text = text
} else {
holder.text.setText(textRes) holder.text.setText(textRes)
}
holder.text.setTextColor(tintColor) holder.text.setTextColor(tintColor)
holder.selected.isInvisible = !selected holder.selected.isInvisible = !selected
if (showExpand) { if (showExpand) {

View File

@ -17,7 +17,6 @@
package im.vector.app.core.epoxy.profiles package im.vector.app.core.epoxy.profiles
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -26,8 +25,10 @@ import androidx.core.widget.ImageViewCompat
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.epoxy.ClickListener
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextOrHide
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.themes.ThemeUtils
@ -67,11 +68,11 @@ abstract class ProfileActionItem : VectorEpoxyModel<ProfileActionItem.Holder>()
var destructive: Boolean = false var destructive: Boolean = false
@EpoxyAttribute @EpoxyAttribute
var listener: View.OnClickListener? = null var listener: ClickListener? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
holder.view.setOnClickListener(listener) holder.view.onClick(listener)
if (listener == null) { if (listener == null) {
holder.view.isClickable = false holder.view.isClickable = false
} }

View File

@ -59,9 +59,7 @@ fun EpoxyController.buildProfileAction(
accessoryRes(accessory) accessoryRes(accessory)
accessoryMatrixItem(accessoryMatrixItem) accessoryMatrixItem(accessoryMatrixItem)
avatarRenderer(avatarRenderer) avatarRenderer(avatarRenderer)
listener { _ -> listener(action)
action?.invoke()
}
} }
if (divider) { if (divider) {

View File

@ -57,3 +57,15 @@ fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_searc
return@OnTouchListener false return@OnTouchListener false
}) })
} }
/**
* Update the edit text value, only if necessary and move the cursor to the end of the text
*/
fun EditText.setTextSafe(value: String?) {
if (value != null && text.toString() != value) {
setText(value)
// To fix jumping cursor to the start https://github.com/airbnb/epoxy/issues/426
// Note: there is still a known bug if deleting char in the middle of the text, by long pressing on the backspace button.
setSelection(value.length)
}
}

View File

@ -21,6 +21,7 @@ import android.net.Uri
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import im.vector.app.core.utils.getFileExtension import im.vector.app.core.utils.getFileExtension
import timber.log.Timber import timber.log.Timber
import java.util.Locale
/** /**
* Returns the mimetype from a uri. * Returns the mimetype from a uri.
@ -44,7 +45,7 @@ fun getMimeTypeFromUri(context: Context, uri: Uri): String? {
if (null != mimeType) { if (null != mimeType) {
// the mimetype is sometimes in uppercase. // the mimetype is sometimes in uppercase.
mimeType = mimeType.toLowerCase() mimeType = mimeType.toLowerCase(Locale.ROOT)
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "Failed to open resource input stream") Timber.e(e, "Failed to open resource input stream")

View File

@ -43,7 +43,7 @@ abstract class VectorViewModel<S : MvRxState, VA : VectorViewModelAction, VE : V
* so you can use this in a switchMap or a flatMap * so you can use this in a switchMap or a flatMap
*/ */
// False positive // False positive
@Suppress("USELESS_CAST") @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER")
fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> { fun <T> Single<T>.toAsync(stateReducer: S.(Async<T>) -> S): Single<Async<T>> {
setState { stateReducer(Loading()) } setState { stateReducer(Loading()) }
return map { Success(it) as Async<T> } return map { Success(it) as Async<T> }
@ -56,7 +56,7 @@ abstract class VectorViewModel<S : MvRxState, VA : VectorViewModelAction, VE : V
* so you can use this in a switchMap or a flatMap * so you can use this in a switchMap or a flatMap
*/ */
// False positive // False positive
@Suppress("USELESS_CAST") @Suppress("USELESS_CAST", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER")
fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> { fun <T> Observable<T>.toAsync(stateReducer: S.(Async<T>) -> S): Observable<Async<T>> {
setState { stateReducer(Loading()) } setState { stateReducer(Loading()) }
return map { Success(it) as Async<T> } return map { Success(it) as Async<T> }

View File

@ -0,0 +1,61 @@
/*
* 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.core.ui.bottomsheet
import android.os.Bundle
import android.view.View
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
import javax.inject.Inject
/**
* Generic Bottom sheet with actions
*/
abstract class BottomSheetGeneric<STATE : BottomSheetGenericState, ACTION : BottomSheetGenericAction> :
VectorBaseBottomSheetDialogFragment(),
BottomSheetGenericController.Listener<ACTION> {
@Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool
@BindView(R.id.bottomSheetRecyclerView)
lateinit var recyclerView: RecyclerView
final override val showExpanded = true
final override fun getLayoutResId() = R.layout.bottom_sheet_generic_list
abstract fun getController(): BottomSheetGenericController<STATE, ACTION>
@CallSuper
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.configureWith(getController(), viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true)
getController().listener = this
}
@CallSuper
override fun onDestroyView() {
recyclerView.cleanup()
getController().listener = null
super.onDestroyView()
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.core.ui.bottomsheet
import androidx.annotation.DrawableRes
import im.vector.app.core.epoxy.bottomsheet.BottomSheetActionItem_
import im.vector.app.core.platform.VectorSharedAction
/**
* Parent class for a bottom sheet action
*/
open class BottomSheetGenericAction(
open val title: String,
@DrawableRes open val iconResId: Int,
open val isSelected: Boolean,
open val destructive: Boolean
) : VectorSharedAction {
fun toBottomSheetItem(): BottomSheetActionItem_ {
return BottomSheetActionItem_().apply {
id("action_$title")
iconRes(iconResId)
text(title)
selected(isSelected)
destructive(destructive)
}
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.core.ui.bottomsheet
import android.view.View
import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.core.epoxy.dividerItem
/**
* Epoxy controller for generic bottom sheet actions
*/
abstract class BottomSheetGenericController<State : BottomSheetGenericState, Action : BottomSheetGenericAction>
: TypedEpoxyController<State>() {
var listener: Listener<Action>? = null
abstract fun getTitle(): String?
open fun getSubTitle(): String? = null
abstract fun getActions(state: State): List<Action>
override fun buildModels(state: State?) {
state ?: return
// Title
getTitle()?.let { title ->
bottomSheetTitleItem {
id("title")
title(title)
subTitle(getSubTitle())
}
dividerItem {
id("title_separator")
}
}
// Actions
val actions = getActions(state)
val showIcons = actions.any { it.iconResId > 0 }
actions.forEach { action ->
action.toBottomSheetItem()
.showIcon(showIcons)
.listener(View.OnClickListener { listener?.didSelectAction(action) })
.addTo(this)
}
}
interface Listener<Action> {
fun didSelectAction(action: Action)
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.core.ui.bottomsheet
import im.vector.app.core.platform.VectorSharedAction
import im.vector.app.core.platform.VectorSharedActionViewModel
/**
* Activity shared view model to handle bottom sheet quick actions
*/
abstract class BottomSheetGenericSharedActionViewModel<Action : VectorSharedAction> : VectorSharedActionViewModel<Action>()

View File

@ -0,0 +1,21 @@
/*
* 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.core.ui.bottomsheet
import com.airbnb.mvrx.MvRxState
abstract class BottomSheetGenericState : MvRxState

View File

@ -0,0 +1,30 @@
/*
* 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.core.ui.bottomsheet
import com.airbnb.mvrx.MvRxState
import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
abstract class BottomSheetGenericViewModel<State : MvRxState>(initialState: State) :
VectorViewModel<State, EmptyAction, EmptyViewEvents>(initialState) {
override fun handle(action: EmptyAction) {
// No op
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.core.ui.bottomsheet
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextOrHide
/**
* A title for bottom sheet, with an optional subtitle. It does not include the bottom separator.
*/
@EpoxyModelClass(layout = R.layout.item_bottom_sheet_title)
abstract class BottomSheetTitleItem : VectorEpoxyModel<BottomSheetTitleItem.Holder>() {
@EpoxyAttribute
lateinit var title: String
@EpoxyAttribute
var subTitle: String? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.title.text = title
holder.subtitle.setTextOrHide(subTitle)
}
class Holder : VectorEpoxyHolder() {
val title by bind<TextView>(R.id.itemBottomSheetTitleTitle)
val subtitle by bind<TextView>(R.id.itemBottomSheetTitleSubtitle)
}
}

View File

@ -44,7 +44,7 @@ open class BehaviorDataSource<T>(private val defaultValue: T? = null) : MutableD
} }
override fun post(value: T) { override fun post(value: T) {
behaviorRelay.accept(value) behaviorRelay.accept(value!!)
} }
private fun createRelay(): BehaviorRelay<T> { private fun createRelay(): BehaviorRelay<T> {
@ -68,6 +68,6 @@ open class PublishDataSource<T> : MutableDataSource<T> {
} }
override fun post(value: T) { override fun post(value: T) {
publishRelay.accept(value) publishRelay.accept(value!!)
} }
} }

View File

@ -19,6 +19,7 @@ package im.vector.app.core.utils
import android.content.Context import android.content.Context
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.util.Locale
// Implementation should return true in case of success // Implementation should return true in case of success
typealias ActionOnFile = (file: File) -> Boolean typealias ActionOnFile = (file: File) -> Boolean
@ -113,7 +114,7 @@ fun getFileExtension(fileUri: String): String? {
val ext = filename.substring(dotPos + 1) val ext = filename.substring(dotPos + 1)
if (ext.isNotBlank()) { if (ext.isNotBlank()) {
return ext.toLowerCase() return ext.toLowerCase(Locale.ROOT)
} }
} }
} }

View File

@ -59,7 +59,6 @@ data class AttachmentsPreviewArgs(
) : Parcelable ) : Parcelable
class AttachmentsPreviewFragment @Inject constructor( class AttachmentsPreviewFragment @Inject constructor(
val viewModelFactory: AttachmentsPreviewViewModel.Factory,
private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController, private val attachmentMiniaturePreviewController: AttachmentMiniaturePreviewController,
private val attachmentBigPreviewController: AttachmentBigPreviewController, private val attachmentBigPreviewController: AttachmentBigPreviewController,
private val colorProvider: ColorProvider private val colorProvider: ColorProvider

View File

@ -17,31 +17,12 @@
package im.vector.app.features.attachments.preview package im.vector.app.features.attachments.preview
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialState: AttachmentsPreviewViewState) class AttachmentsPreviewViewModel(initialState: AttachmentsPreviewViewState)
: VectorViewModel<AttachmentsPreviewViewState, AttachmentsPreviewAction, AttachmentsPreviewViewEvents>(initialState) { : VectorViewModel<AttachmentsPreviewViewState, AttachmentsPreviewAction, AttachmentsPreviewViewEvents>(initialState) {
@AssistedInject.Factory
interface Factory {
fun create(initialState: AttachmentsPreviewViewState): AttachmentsPreviewViewModel
}
companion object : MvRxViewModelFactory<AttachmentsPreviewViewModel, AttachmentsPreviewViewState> {
@JvmStatic
override fun create(viewModelContext: ViewModelContext, state: AttachmentsPreviewViewState): AttachmentsPreviewViewModel? {
val fragment: AttachmentsPreviewFragment = (viewModelContext as FragmentViewModelContext).fragment()
return fragment.viewModelFactory.create(state)
}
}
override fun handle(action: AttachmentsPreviewAction) { override fun handle(action: AttachmentsPreviewAction) {
when (action) { when (action) {
is AttachmentsPreviewAction.SetCurrentAttachment -> handleSetCurrentAttachment(action) is AttachmentsPreviewAction.SetCurrentAttachment -> handleSetCurrentAttachment(action)

View File

@ -0,0 +1,25 @@
/*
* 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.crypto.verification
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import javax.inject.Inject
class QuadSLoadingFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_progress
}

View File

@ -31,5 +31,6 @@ sealed class VerificationAction : VectorViewModelAction {
object SkipVerification : VerificationAction() object SkipVerification : VerificationAction()
object VerifyFromPassphrase : VerificationAction() object VerifyFromPassphrase : VerificationAction()
data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction()
object CancelledFromSsss : VerificationAction()
object SecuredStorageHasBeenReset : VerificationAction() object SecuredStorageHasBeenReset : VerificationAction()
} }

View File

@ -155,6 +155,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
// all have been reset, so we are verified? // all have been reset, so we are verified?
viewModel.handle(VerificationAction.SecuredStorageHasBeenReset) viewModel.handle(VerificationAction.SecuredStorageHasBeenReset)
} }
} else {
viewModel.handle(VerificationAction.CancelledFromSsss)
} }
} }
@ -209,6 +211,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
return@withState return@withState
} }
if (state.selfVerificationMode && state.verifyingFrom4S) {
showFragment(QuadSLoadingFragment::class, Bundle())
return@withState
}
if (state.selfVerificationMode && state.verifiedFromPrivateKeys) { if (state.selfVerificationMode && state.verifiedFromPrivateKeys) {
showFragment(VerificationConclusionFragment::class, Bundle().apply { showFragment(VerificationConclusionFragment::class, Bundle().apply {
putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe))

View File

@ -32,6 +32,7 @@ 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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
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
@ -70,6 +71,7 @@ data class VerificationBottomSheetViewState(
// true when we display the loading and we wait for the other (incoming request) // true when we display the loading and we wait for the other (incoming request)
val selfVerificationMode: Boolean = false, val selfVerificationMode: Boolean = false,
val verifiedFromPrivateKeys: Boolean = false, val verifiedFromPrivateKeys: Boolean = false,
val verifyingFrom4S: Boolean = false,
val isMe: Boolean = false, val isMe: Boolean = false,
val currentDeviceCanCrossSign: Boolean = false, val currentDeviceCanCrossSign: Boolean = false,
val userWantsToCancel: Boolean = false, val userWantsToCancel: Boolean = false,
@ -170,7 +172,9 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
} }
} else { } else {
// if the verification is already done you can't cancel anymore // if the verification is already done you can't cancel anymore
if (state.pendingRequest.invoke()?.cancelConclusion != null || state.sasTransactionState is VerificationTxState.TerminalTxState) { if (state.pendingRequest.invoke()?.cancelConclusion != null
|| state.sasTransactionState is VerificationTxState.TerminalTxState
|| state.verifyingFrom4S) {
// you cannot cancel anymore // you cannot cancel anymore
} else { } else {
setState { setState {
@ -346,6 +350,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
} }
is VerificationAction.VerifyFromPassphrase -> { is VerificationAction.VerifyFromPassphrase -> {
setState { copy(verifyingFrom4S = true) }
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore) _viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
} }
is VerificationAction.GotResultFromSsss -> { is VerificationAction.GotResultFromSsss -> {
@ -354,15 +359,21 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
VerificationAction.SecuredStorageHasBeenReset -> { VerificationAction.SecuredStorageHasBeenReset -> {
if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) { if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) {
setState { setState {
copy(quadSHasBeenReset = true) copy(quadSHasBeenReset = true, verifyingFrom4S = false)
} }
} }
Unit Unit
} }
VerificationAction.CancelledFromSsss -> {
setState {
copy(verifyingFrom4S = false)
}
}
}.exhaustive }.exhaustive
} }
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) { private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
viewModelScope.launch(Dispatchers.IO) {
try { try {
action.cypherData.fromBase64().inputStream().use { ins -> action.cypherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<Map<String, String>>(ins, action.alias) val res = session.loadSecureSecret<Map<String, String>>(ins, action.alias)
@ -383,27 +394,38 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
} }
setState { setState {
copy(verifiedFromPrivateKeys = true) copy(
} verifyingFrom4S = false,
verifiedFromPrivateKeys = true
// try to get keybackup key )
} else {
// POP UP something
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys)))
} }
// try the keybackup // try the keybackup
tentativeRestoreBackup(res) tentativeRestoreBackup(res)
Unit } else {
setState {
copy(
verifyingFrom4S = false
)
}
// POP UP something
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys)))
}
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
setState {
copy(
verifyingFrom4S = false
)
}
_viewEvents.post( _viewEvents.post(
VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))) VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error)))
} }
} }
}
private fun tentativeRestoreBackup(res: Map<String, String>?) { private fun tentativeRestoreBackup(res: Map<String, String>?) {
viewModelScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
try { try {
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
Timber.v("## Keybackup secret not restored from SSSS") Timber.v("## Keybackup secret not restored from SSSS")

View File

@ -27,6 +27,9 @@ import im.vector.app.core.epoxy.onClick
@EpoxyModelClass(layout = R.layout.item_settings_continue_cancel) @EpoxyModelClass(layout = R.layout.item_settings_continue_cancel)
abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinueCancelItem.Holder>() { abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinueCancelItem.Holder>() {
@EpoxyAttribute
var continueText: String? = null
@EpoxyAttribute @EpoxyAttribute
var continueOnClick: ClickListener? = null var continueOnClick: ClickListener? = null
@ -37,6 +40,8 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
super.bind(holder) super.bind(holder)
holder.cancelButton.onClick(cancelOnClick) holder.cancelButton.onClick(cancelOnClick)
continueText?.let { holder.continueButton.text = it }
holder.continueButton.onClick(continueOnClick) holder.continueButton.onClick(continueOnClick)
} }

View File

@ -31,7 +31,6 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.terms.GetTermsResponse
import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.internal.util.awaitCallback
import java.net.UnknownHostException import java.net.UnknownHostException
@ -117,9 +116,7 @@ class SetIdentityServerViewModel @AssistedInject constructor(
private suspend fun checkTerms(baseUrl: String) { private suspend fun checkTerms(baseUrl: String) {
try { try {
val data = awaitCallback<GetTermsResponse> { val data = mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl)
mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl, it)
}
// has all been accepted? // has all been accepted?
val resp = data.serverResponse val resp = data.serverResponse

View File

@ -26,6 +26,7 @@ import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_form_text_input) @EpoxyModelClass(layout = R.layout.item_form_text_input)
@ -65,9 +66,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
holder.textInputLayout.error = errorMessage holder.textInputLayout.error = errorMessage
// Update only if text is different and value is not null // Update only if text is different and value is not null
if (value != null && holder.textInputEditText.text.toString() != value) { holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.setText(value)
}
holder.textInputEditText.isEnabled = enabled holder.textInputEditText.isEnabled = enabled
inputType?.let { holder.textInputEditText.inputType = it } inputType?.let { holder.textInputEditText.inputType = it }

View File

@ -26,6 +26,7 @@ import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_form_text_input_with_button) @EpoxyModelClass(layout = R.layout.item_form_text_input_with_button)
@ -61,9 +62,7 @@ abstract class FormEditTextWithButtonItem : VectorEpoxyModel<FormEditTextWithBut
holder.textInputLayout.hint = hint holder.textInputLayout.hint = hint
// Update only if text is different // Update only if text is different
if (holder.textInputEditText.text.toString() != value) { holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.setText(value)
}
holder.textInputEditText.isEnabled = enabled holder.textInputEditText.isEnabled = enabled
holder.textInputEditText.addTextChangedListener(onTextChangeListener) holder.textInputEditText.addTextChangedListener(onTextChangeListener)

View File

@ -61,8 +61,8 @@ abstract class FormSwitchItem : VectorEpoxyModel<FormSwitchItem.Holder>() {
holder.switchView.isEnabled = enabled holder.switchView.isEnabled = enabled
holder.switchView.setOnCheckedChangeListener(null)
holder.switchView.isChecked = switchChecked holder.switchView.isChecked = switchChecked
holder.switchView.setOnCheckedChangeListener { _, isChecked -> holder.switchView.setOnCheckedChangeListener { _, isChecked ->
listener?.invoke(isChecked) listener?.invoke(isChecked)
} }

View File

@ -53,7 +53,6 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_ROOM_AVATAR, EventType.STATE_ROOM_AVATAR,
EventType.STATE_ROOM_MEMBER, EventType.STATE_ROOM_MEMBER,
EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STATE_ROOM_ALIASES,
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,
@ -79,6 +78,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
encryptedItemFactory.create(event, nextEvent, highlight, callback) encryptedItemFactory.create(event, nextEvent, highlight, callback)
} }
} }
EventType.STATE_ROOM_ALIASES,
EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_KEY,

View File

@ -260,13 +260,13 @@ class NoticeEventFormatter @Inject constructor(
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
val formattedVisibility = roomHistoryVisibilityFormatter.format(historyVisibility) val historyVisibilitySuffix = roomHistoryVisibilityFormatter.getNoticeSuffix(historyVisibility)
return if (event.isSentByCurrentUser()) { return if (event.isSentByCurrentUser()) {
sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you, sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you,
formattedVisibility) historyVisibilitySuffix)
} else { } else {
sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility, sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility,
senderName, formattedVisibility) senderName, historyVisibilitySuffix)
} }
} }
@ -465,22 +465,73 @@ class NoticeEventFormatter @Inject constructor(
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 prevContent: RoomCanonicalAliasContent? = event.resolvedPrevContent().toModel()
return canonicalAlias val canonicalAlias = eventContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
?.takeIf { it.isNotBlank() } val prevCanonicalAlias = prevContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
?.let { val altAliases = eventContent?.alternativeAliases.orEmpty()
val prevAltAliases = prevContent?.alternativeAliases.orEmpty()
val added = altAliases - prevAltAliases
val removed = prevAltAliases - altAliases
return when {
added.isEmpty() && removed.isEmpty() && canonicalAlias == prevCanonicalAlias -> {
// No difference between the two events say something as we can't simply hide the event from here
if (event.isSentByCurrentUser()) { if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_set_by_you, it) sp.getString(R.string.notice_room_canonical_alias_no_change_by_you)
} else { } else {
sp.getString(R.string.notice_room_canonical_alias_set, senderName, it) sp.getString(R.string.notice_room_canonical_alias_no_change, senderName)
} }
} }
?: if (event.isSentByCurrentUser()) { added.isEmpty() && removed.isEmpty() -> {
// Canonical has changed
if (canonicalAlias != null) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_set_by_you, canonicalAlias)
} else {
sp.getString(R.string.notice_room_canonical_alias_set, senderName, canonicalAlias)
}
} else {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_unset_by_you) sp.getString(R.string.notice_room_canonical_alias_unset_by_you)
} else { } else {
sp.getString(R.string.notice_room_canonical_alias_unset, senderName) sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
} }
} }
}
added.isEmpty() && canonicalAlias == prevCanonicalAlias -> {
// Some alternative has been removed
if (event.isSentByCurrentUser()) {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed_by_you, removed.size, removed.joinToString())
} else {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed, removed.size, senderName, removed.joinToString())
}
}
removed.isEmpty() && canonicalAlias == prevCanonicalAlias -> {
// Some alternative has been added
if (event.isSentByCurrentUser()) {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added_by_you, added.size, added.joinToString())
} else {
sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added, added.size, senderName, added.joinToString())
}
}
canonicalAlias == prevCanonicalAlias -> {
// Alternative added and removed
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_alternative_changed_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_alternative_changed, senderName)
}
}
else -> {
// Main and removed, or main and added, or main and added and removed
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed, senderName)
}
}
}
}
private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, rs: RoomSummary?): String? { private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, rs: RoomSummary?): String? {
val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel() val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel()

View File

@ -24,13 +24,21 @@ import javax.inject.Inject
class RoomHistoryVisibilityFormatter @Inject constructor( class RoomHistoryVisibilityFormatter @Inject constructor(
private val stringProvider: StringProvider private val stringProvider: StringProvider
) { ) {
fun getNoticeSuffix(roomHistoryVisibility: RoomHistoryVisibility): String {
fun format(roomHistoryVisibility: RoomHistoryVisibility): String { return stringProvider.getString(when (roomHistoryVisibility) {
return when (roomHistoryVisibility) { RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) RoomHistoryVisibility.SHARED -> R.string.notice_room_visibility_shared
RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) RoomHistoryVisibility.INVITED -> R.string.notice_room_visibility_invited
RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) RoomHistoryVisibility.JOINED -> R.string.notice_room_visibility_joined
RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) })
} }
fun getSetting(roomHistoryVisibility: RoomHistoryVisibility): String {
return stringProvider.getString(when (roomHistoryVisibility) {
RoomHistoryVisibility.WORLD_READABLE -> R.string.room_settings_read_history_entry_anyone
RoomHistoryVisibility.SHARED -> R.string.room_settings_read_history_entry_members_only_option_time_shared
RoomHistoryVisibility.INVITED -> R.string.room_settings_read_history_entry_members_only_invited
RoomHistoryVisibility.JOINED -> R.string.room_settings_read_history_entry_members_only_joined
})
} }
} }

View File

@ -77,6 +77,7 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R
override fun onDestroyView() { override fun onDestroyView() {
recyclerView.cleanup() recyclerView.cleanup()
roomListActionsEpoxyController.listener = null
super.onDestroyView() super.onDestroyView()
} }

View File

@ -56,6 +56,7 @@ class PinFragment @Inject constructor(
when (fragmentArgs.pinMode) { when (fragmentArgs.pinMode) {
PinMode.CREATE -> showCreateFragment() PinMode.CREATE -> showCreateFragment()
PinMode.AUTH -> showAuthFragment() PinMode.AUTH -> showAuthFragment()
PinMode.MODIFY -> showCreateFragment() // No need to create another function for now because texts are generic
} }
} }
@ -73,6 +74,10 @@ class PinFragment @Inject constructor(
Toast.makeText(requireContext(), getString(R.string.create_pin_confirm_failure), Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), getString(R.string.create_pin_confirm_failure), Toast.LENGTH_SHORT).show()
} }
override fun onPinCodeEnteredFirst(pinCode: String?): Boolean {
return false
}
override fun onCodeCreated(encodedCode: String) { override fun onCodeCreated(encodedCode: String) {
lifecycleScope.launch { lifecycleScope.launch {
pinCodeStore.storeEncodedPin(encodedCode) pinCodeStore.storeEncodedPin(encodedCode)

View File

@ -18,5 +18,6 @@ package im.vector.app.features.pin
enum class PinMode { enum class PinMode {
CREATE, CREATE,
AUTH AUTH,
MODIFY
} }

View File

@ -20,7 +20,6 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsSectionTitleItem import im.vector.app.features.discovery.settingsSectionTitleItem
import im.vector.app.features.form.formAdvancedToggleItem import im.vector.app.features.form.formAdvancedToggleItem
@ -31,8 +30,9 @@ import im.vector.app.features.form.formSwitchItem
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import javax.inject.Inject import javax.inject.Inject
class CreateRoomController @Inject constructor(private val stringProvider: StringProvider, class CreateRoomController @Inject constructor(
private val errorFormatter: ErrorFormatter private val stringProvider: StringProvider,
private val roomAliasErrorFormatter: RoomAliasErrorFormatter
) : TypedEpoxyController<CreateRoomViewState>() { ) : TypedEpoxyController<CreateRoomViewState>() {
var listener: Listener? = null var listener: Listener? = null
@ -104,13 +104,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
value(viewState.roomType.aliasLocalPart) value(viewState.roomType.aliasLocalPart)
homeServer(":" + viewState.homeServerName) homeServer(":" + viewState.homeServerName)
errorMessage( errorMessage(
when ((viewState.asyncCreateRoomRequest as? Fail)?.error) { roomAliasErrorFormatter.format(
is CreateRoomFailure.RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty (((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError)
is CreateRoomFailure.RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use
is CreateRoomFailure.RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid
else -> null
}
?.let { stringProvider.getString(it) }
) )
onTextChange { value -> onTextChange { value ->
listener?.setAliasLocalPart(value) listener?.setAliasLocalPart(value)

View File

@ -84,7 +84,7 @@ class CreateRoomFragment @Inject constructor(
override fun showFailure(throwable: Throwable) { override fun showFailure(throwable: Throwable) {
// Note: RoomAliasError are displayed directly in the form // Note: RoomAliasError are displayed directly in the form
if (throwable !is CreateRoomFailure.RoomAliasError) { if (throwable !is CreateRoomFailure.AliasError) {
super.showFailure(throwable) super.showFailure(throwable)
} }
} }

View File

@ -27,6 +27,7 @@ import com.google.android.material.textfield.TextInputLayout
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_room_alias_text_input) @EpoxyModelClass(layout = R.layout.item_room_alias_text_input)
@ -62,9 +63,7 @@ abstract class RoomAliasEditItem : VectorEpoxyModel<RoomAliasEditItem.Holder>()
holder.textInputLayout.error = errorMessage holder.textInputLayout.error = errorMessage
// Update only if text is different and value is not null // Update only if text is different and value is not null
if (value != null && holder.textInputEditText.text.toString() != value) { holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.setText(value)
}
holder.textInputEditText.isEnabled = enabled holder.textInputEditText.isEnabled = enabled
holder.textInputEditText.addTextChangedListener(onTextChangeListener) holder.textInputEditText.addTextChangedListener(onTextChangeListener)
holder.homeServerText.text = homeServer holder.homeServerText.text = homeServer

View File

@ -0,0 +1,36 @@
/*
* 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.roomdirectory.createroom
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import javax.inject.Inject
class RoomAliasErrorFormatter @Inject constructor(
private val stringProvider: StringProvider
) {
fun format(roomAliasError: RoomAliasError?): String? {
return when (roomAliasError) {
is RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty
is RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use
is RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid
else -> null
}
?.let { stringProvider.getString(it) }
}
}

View File

@ -36,6 +36,7 @@ import im.vector.app.features.room.RequireActiveMembershipViewState
import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment
import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment
import im.vector.app.features.roomprofile.alias.RoomAliasFragment
import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment
import javax.inject.Inject import javax.inject.Inject
@ -100,6 +101,7 @@ class RoomProfileActivity :
when (sharedAction) { when (sharedAction) {
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
is RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias()
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
} }
@ -135,6 +137,10 @@ class RoomProfileActivity :
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs) addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
} }
private fun openRoomAlias() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs)
}
private fun openRoomMembers() { private fun openRoomMembers() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs)
} }

View File

@ -23,6 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction
*/ */
sealed class RoomProfileSharedAction : VectorSharedAction { sealed class RoomProfileSharedAction : VectorSharedAction {
object OpenRoomSettings : RoomProfileSharedAction() object OpenRoomSettings : RoomProfileSharedAction()
object OpenRoomAliasesSettings : RoomProfileSharedAction()
object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction()
object OpenRoomMembers : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction()
object OpenBannedRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction()

View File

@ -0,0 +1,39 @@
/*
* 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.
*/
package im.vector.app.features.roomprofile.alias
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
sealed class RoomAliasAction : VectorViewModelAction {
// Canonical
object ToggleManualPublishForm : RoomAliasAction()
data class SetNewAlias(val alias: String) : RoomAliasAction()
object ManualPublishAlias : RoomAliasAction()
data class PublishAlias(val alias: String) : RoomAliasAction()
data class UnpublishAlias(val alias: String) : RoomAliasAction()
data class SetCanonicalAlias(val canonicalAlias: String?) : RoomAliasAction()
// Room directory
data class SetRoomDirectoryVisibility(val roomDirectoryVisibility: RoomDirectoryVisibility) : RoomAliasAction()
// Local
data class RemoveLocalAlias(val alias: String) : RoomAliasAction()
object ToggleAddLocalAliasForm : RoomAliasAction()
data class SetNewLocalAliasLocalPart(val aliasLocalPart: String) : RoomAliasAction()
object AddLocalAlias : RoomAliasAction()
}

View File

@ -0,0 +1,262 @@
/*
* 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.
*/
package im.vector.app.features.roomprofile.alias
import android.text.InputType
import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.core.epoxy.errorWithRetryItem
import im.vector.app.core.epoxy.loadingItem
import im.vector.app.core.epoxy.profiles.buildProfileSection
import im.vector.app.core.epoxy.profiles.profileActionItem
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsButtonItem
import im.vector.app.features.discovery.settingsContinueCancelItem
import im.vector.app.features.discovery.settingsInfoItem
import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter
import im.vector.app.features.roomdirectory.createroom.roomAliasEditItem
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import javax.inject.Inject
class RoomAliasController @Inject constructor(
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val colorProvider: ColorProvider,
private val roomAliasErrorFormatter: RoomAliasErrorFormatter
) : TypedEpoxyController<RoomAliasViewState>() {
interface Callback {
fun toggleManualPublishForm()
fun setNewAlias(alias: String)
fun addAlias()
fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility)
fun toggleLocalAliasForm()
fun setNewLocalAliasLocalPart(aliasLocalPart: String)
fun addLocalAlias()
fun openAliasDetail(alias: String)
}
var callback: Callback? = null
init {
setData(null)
}
override fun buildModels(data: RoomAliasViewState?) {
data ?: return
// Published alias
buildPublishInfo(data)
// Room directory visibility
buildRoomDirectoryVisibility(data)
// Local alias
buildLocalInfo(data)
}
private fun buildRoomDirectoryVisibility(data: RoomAliasViewState) {
when (data.roomDirectoryVisibility) {
Uninitialized -> Unit
is Loading -> Unit
is Success -> {
formSwitchItem {
id("roomVisibility")
title(stringProvider.getString(R.string.room_alias_publish_to_directory, data.homeServerName))
showDivider(false)
switchChecked(data.roomDirectoryVisibility() == RoomDirectoryVisibility.PUBLIC)
listener {
if (it) {
callback?.setRoomDirectoryVisibility(RoomDirectoryVisibility.PUBLIC)
} else {
callback?.setRoomDirectoryVisibility(RoomDirectoryVisibility.PRIVATE)
}
}
}
}
is Fail -> {
errorWithRetryItem {
text(stringProvider.getString(R.string.room_alias_publish_to_directory_error,
errorFormatter.toHumanReadable(data.roomDirectoryVisibility.error)))
}
}
}
}
private fun buildPublishInfo(data: RoomAliasViewState) {
buildProfileSection(
stringProvider.getString(R.string.room_alias_published_alias_title)
)
settingsInfoItem {
id("publishedInfo")
helperTextResId(R.string.room_alias_published_alias_subtitle)
}
data.canonicalAlias
?.takeIf { it.isNotEmpty() }
?.let { canonicalAlias ->
profileActionItem {
id("canonical")
title(data.canonicalAlias)
subtitle(stringProvider.getString(R.string.room_alias_published_alias_main))
listener { callback?.openAliasDetail(canonicalAlias) }
}
}
if (data.alternativeAliases.isEmpty()) {
settingsInfoItem {
id("otherPublishedEmpty")
if (data.actionPermissions.canChangeCanonicalAlias) {
helperTextResId(R.string.room_alias_address_empty_can_add)
} else {
helperTextResId(R.string.room_alias_address_empty)
}
}
} else {
settingsInfoItem {
id("otherPublished")
helperTextResId(R.string.room_alias_published_other)
}
data.alternativeAliases.forEachIndexed { idx, altAlias ->
profileActionItem {
id("alt_$idx")
title(altAlias)
listener { callback?.openAliasDetail(altAlias) }
}
}
}
if (data.actionPermissions.canChangeCanonicalAlias) {
buildPublishManuallyForm(data)
}
}
private fun buildPublishManuallyForm(data: RoomAliasViewState) {
when (data.publishManuallyState) {
RoomAliasViewState.AddAliasState.Hidden -> Unit
RoomAliasViewState.AddAliasState.Closed -> {
settingsButtonItem {
id("publishManually")
colorProvider(colorProvider)
buttonTitleId(R.string.room_alias_published_alias_add_manually)
buttonClickListener { callback?.toggleManualPublishForm() }
}
}
is RoomAliasViewState.AddAliasState.Editing -> {
formEditTextItem {
id("publishManuallyEdit")
value(data.publishManuallyState.value)
showBottomSeparator(false)
hint(stringProvider.getString(R.string.room_alias_address_hint))
inputType(InputType.TYPE_CLASS_TEXT)
onTextChange { text ->
callback?.setNewAlias(text)
}
}
settingsContinueCancelItem {
id("publishManuallySubmit")
continueText(stringProvider.getString(R.string.room_alias_published_alias_add_manually_submit))
continueOnClick { callback?.addAlias() }
cancelOnClick { callback?.toggleManualPublishForm() }
}
}
}
}
private fun buildLocalInfo(data: RoomAliasViewState) {
buildProfileSection(
stringProvider.getString(R.string.room_alias_local_address_title)
)
settingsInfoItem {
id("localInfo")
helperText(stringProvider.getString(R.string.room_alias_local_address_subtitle, data.homeServerName))
}
when (val localAliases = data.localAliases) {
is Uninitialized -> {
loadingItem {
id("loadingAliases")
}
}
is Success -> {
if (localAliases().isEmpty()) {
settingsInfoItem {
id("locEmpty")
helperTextResId(R.string.room_alias_local_address_empty)
}
} else {
localAliases().forEachIndexed { idx, localAlias ->
profileActionItem {
id("loc_$idx")
title(localAlias)
listener { callback?.openAliasDetail(localAlias) }
}
}
}
}
is Fail -> {
errorWithRetryItem {
id("alt_error")
text(errorFormatter.toHumanReadable(localAliases.error))
}
}
}
// Add local
buildAddLocalAlias(data)
}
private fun buildAddLocalAlias(data: RoomAliasViewState) {
when (data.newLocalAliasState) {
RoomAliasViewState.AddAliasState.Hidden -> Unit
RoomAliasViewState.AddAliasState.Closed -> {
settingsButtonItem {
id("newLocalAliasButton")
colorProvider(colorProvider)
buttonTitleId(R.string.room_alias_local_address_add)
buttonClickListener { callback?.toggleLocalAliasForm() }
}
}
is RoomAliasViewState.AddAliasState.Editing -> {
roomAliasEditItem {
id("newLocalAlias")
value(data.newLocalAliasState.value)
homeServer(":" + data.homeServerName)
showBottomSeparator(false)
errorMessage(roomAliasErrorFormatter.format((data.newLocalAliasState.asyncRequest as? Fail)?.error as? RoomAliasError))
onTextChange { value ->
callback?.setNewLocalAliasLocalPart(value)
}
}
settingsContinueCancelItem {
id("newLocalAliasSubmit")
continueText(stringProvider.getString(R.string.action_add))
continueOnClick { callback?.addLocalAlias() }
cancelOnClick { callback?.toggleLocalAliasForm() }
}
}
}
}
}

View File

@ -0,0 +1,193 @@
/*
* 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.
*/
package im.vector.app.features.roomprofile.alias
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.dialogs.withColoredButton
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.shareText
import im.vector.app.core.utils.toast
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomprofile.RoomProfileArgs
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction
import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel
import kotlinx.android.synthetic.main.fragment_room_setting_generic.*
import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
class RoomAliasFragment @Inject constructor(
val viewModelFactory: RoomAliasViewModel.Factory,
private val controller: RoomAliasController,
private val avatarRenderer: AvatarRenderer
) :
VectorBaseFragment(),
RoomAliasController.Callback {
private val viewModel: RoomAliasViewModel by fragmentViewModel()
private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel
private val roomProfileArgs: RoomProfileArgs by args()
override fun getLayoutResId() = R.layout.fragment_room_setting_generic
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java)
controller.callback = this
setupToolbar(roomSettingsToolbar)
roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true)
waiting_view_status_text.setText(R.string.please_wait)
waiting_view_status_text.isVisible = true
viewModel.observeViewEvents {
when (it) {
is RoomAliasViewEvents.Failure -> showFailure(it.throwable)
RoomAliasViewEvents.Success -> showSuccess()
}.exhaustive
}
sharedActionViewModel
.observe()
.subscribe { handleAliasAction(it) }
.disposeOnDestroyView()
}
private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) {
when (action) {
is RoomAliasBottomSheetSharedAction.ShareAlias -> shareAlias(action.matrixTo)
is RoomAliasBottomSheetSharedAction.PublishAlias -> viewModel.handle(RoomAliasAction.PublishAlias(action.alias))
is RoomAliasBottomSheetSharedAction.UnPublishAlias -> unpublishAlias(action.alias)
is RoomAliasBottomSheetSharedAction.DeleteAlias -> removeLocalAlias(action.alias)
is RoomAliasBottomSheetSharedAction.SetMainAlias -> viewModel.handle(RoomAliasAction.SetCanonicalAlias(action.alias))
RoomAliasBottomSheetSharedAction.UnsetMainAlias -> viewModel.handle(RoomAliasAction.SetCanonicalAlias(canonicalAlias = null))
null -> Unit
}
}
private fun shareAlias(matrixTo: String) {
shareText(requireContext(), matrixTo)
}
override fun showFailure(throwable: Throwable) {
if (throwable !is RoomAliasError) {
super.showFailure(throwable)
}
}
private fun showSuccess() {
activity?.toast(R.string.room_settings_save_success)
}
override fun onDestroyView() {
controller.callback = null
roomSettingsRecyclerView.cleanup()
super.onDestroyView()
}
override fun invalidate() = withState(viewModel) { state ->
waiting_view.isVisible = state.isLoading
controller.setData(state)
renderRoomSummary(state)
}
private fun renderRoomSummary(state: RoomAliasViewState) {
state.roomSummary()?.let {
roomSettingsToolbarTitleView.text = it.displayName
avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView)
}
}
private fun unpublishAlias(alias: String) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_confirmation)
.setMessage(getString(R.string.room_alias_unpublish_confirmation, alias))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.action_unpublish) { _, _ ->
viewModel.handle(RoomAliasAction.UnpublishAlias(alias))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
override fun toggleManualPublishForm() {
viewModel.handle(RoomAliasAction.ToggleManualPublishForm)
}
override fun setNewAlias(alias: String) {
viewModel.handle(RoomAliasAction.SetNewAlias(alias))
}
override fun addAlias() {
viewModel.handle(RoomAliasAction.ManualPublishAlias)
}
override fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility) {
viewModel.handle(RoomAliasAction.SetRoomDirectoryVisibility(roomDirectoryVisibility))
}
override fun toggleLocalAliasForm() {
viewModel.handle(RoomAliasAction.ToggleAddLocalAliasForm)
}
override fun setNewLocalAliasLocalPart(aliasLocalPart: String) {
viewModel.handle(RoomAliasAction.SetNewLocalAliasLocalPart(aliasLocalPart))
}
override fun addLocalAlias() {
viewModel.handle(RoomAliasAction.AddLocalAlias)
}
override fun openAliasDetail(alias: String) = withState(viewModel) { state ->
RoomAliasBottomSheet
.newInstance(
alias = alias,
isPublished = alias in state.allPublishedAliases,
isMainAlias = alias == state.canonicalAlias,
isLocal = alias in state.localAliases().orEmpty(),
canEditCanonicalAlias = state.actionPermissions.canChangeCanonicalAlias
)
.show(childFragmentManager, "ROOM_ALIAS_ACTIONS")
}
private fun removeLocalAlias(alias: String) {
AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_title_confirmation)
.setMessage(getString(R.string.room_alias_delete_confirmation, alias))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias))
}
.show()
.withColoredButton(DialogInterface.BUTTON_POSITIVE)
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.
*
*/
package im.vector.app.features.roomprofile.alias
import im.vector.app.core.platform.VectorViewEvents
/**
* Transient events for room settings screen
*/
sealed class RoomAliasViewEvents : VectorViewEvents {
data class Failure(val throwable: Throwable) : RoomAliasViewEvents()
object Success : RoomAliasViewEvents()
}

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