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>sygnal</w>
<w>threepid</w>
<w>unpublish</w>
<w>unwedging</w>
</words>
</dictionary>

View File

@ -2,13 +2,17 @@ Changes in Element 1.0.12 (2020-XX-XX)
===================================================
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 🙌:
-
- Add Setting Item to Change PIN (#2462)
- Improve room history visibility setting UX (#1579)
Bugfix 🐛:
- Fix cancellation of sending event (#2438)
- Double bottomsheet effect after verify with passphrase
- EditText cursor jumps to the start while typing fast (#2469)
Translations 🗣:
-
@ -17,13 +21,14 @@ SDK API changes ⚠️:
-
Build 🧱:
-
- Upgrade some dependencies and Kotlin version
- Use fragment-ktx and preference-ktx dependencies (fix lint issue KtxExtensionAvailable)
Test:
-
Other changes:
-
- Remove "Status.im" theme #2424
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 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'com.google.android.material:material:1.2.1'

View File

@ -2,8 +2,8 @@
buildscript {
// Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.4.10'
ext.kotlin_coroutines_version = "1.3.9"
ext.kotlin_version = '1.4.20'
ext.kotlin_coroutines_version = "1.4.1"
repositories {
google()
jcenter()
@ -12,7 +12,7 @@ buildscript {
}
}
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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'

View File

@ -36,9 +36,9 @@ android {
dependencies {
implementation project(":matrix-sdk-android")
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:rxandroid:2.1.1'
// Paging
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.Single
fun <T> singleBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Single<T> = Single.create {
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
fun <T> singleBuilder(builder: (MatrixCallback<T>) -> Cancelable): Single<T> = Single.create { emitter ->
val callback = object : MatrixCallback<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) {
it.tryOnError(failure)
emitter.tryOnError(failure)
}
}
val cancelable = builder(callback)
it.setCancellable {
emitter.setCancellable {
cancelable.cancel()
}
}
fun <T> completableBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Completable = Completable.create {
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
fun <T> completableBuilder(builder: (MatrixCallback<T>) -> Cancelable): Completable = Completable.create { emitter ->
val callback = object : MatrixCallback<T> {
override fun onSuccess(data: T) {
it.onComplete()
emitter.onComplete()
}
override fun onFailure(failure: Throwable) {
it.tryOnError(failure)
emitter.tryOnError(failure)
}
}
val cancelable = builder(callback)
it.setCancellable {
emitter.setCancellable {
cancelable.cancel()
}
}

View File

@ -35,6 +35,8 @@ import org.matrix.android.sdk.api.util.toOptional
import io.reactivex.Completable
import io.reactivex.Observable
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) {
@ -127,18 +129,14 @@ class RxRoom(private val room: Room) {
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> {
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> {
room.updateAvatar(avatarUri, fileName, it)
}

View File

@ -125,7 +125,6 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
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.lifecycle:lifecycle-extensions:$lifecycle_version"
@ -146,7 +145,7 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
implementation 'androidx.exifinterface:exifinterface:1.3.0'
implementation 'androidx.exifinterface:exifinterface:1.3.1'
// Database
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_GUEST_ACCESS = "m.room.guest_access"
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_TOMBSTONE = "m.room.tombstone"
const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias"

View File

@ -16,9 +16,6 @@
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.
*/
@ -80,19 +77,17 @@ interface IntegrationManagerService {
/**
* Offers to enable or disable the integration.
* @param enable the param to change
* @param callback the matrix callback to listen for result.
* @return Cancelable
*/
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable
suspend fun setIntegrationEnabled(enable: Boolean)
/**
* Offers to allow or disallow a widget.
* @param stateEventId the eventId of the state event defining the widget.
* @param allowed the param to change
* @param callback the matrix callback to listen for result.
* @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.
@ -105,7 +100,7 @@ interface IntegrationManagerService {
* @param widgetType the widget type 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.

View File

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

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.room
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.PublicRoomsResponse
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.
*/
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,
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.
* 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.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
sealed class CreateRoomFailure : Failure.FeatureFailure() {
object CreatedWithTimeout : CreateRoomFailure()
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
sealed class RoomAliasError : CreateRoomFailure() {
object AliasEmpty : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
}

View File

@ -21,6 +21,9 @@ import com.squareup.moshi.JsonClass
/**
* 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)
data class RoomAliasesContent(

View File

@ -24,5 +24,14 @@ import com.squareup.moshi.JsonClass
*/
@JsonClass(generateAdapter = true)
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.query.QueryStringValue
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.RoomJoinRules
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional
@ -38,21 +40,23 @@ interface StateService {
*/
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
* @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
*/
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
*/

View File

@ -16,9 +16,6 @@
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.
*/
@ -29,7 +26,5 @@ interface UploadsService {
* @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
*/
fun getUploads(numberOfEvents: Int,
since: String?,
callback: MatrixCallback<GetUploadsResult>): Cancelable
suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult
}

View File

@ -16,22 +16,16 @@
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 {
enum class ServiceType {
IntegrationManager,
IdentityService
}
fun getTerms(serviceType: ServiceType,
baseUrl: String,
callback: MatrixCallback<GetTermsResponse>): Cancelable
suspend fun getTerms(serviceType: ServiceType, baseUrl: String): GetTermsResponse
fun agreeToTerms(serviceType: ServiceType,
baseUrl: String,
agreedUrls: List<String>,
token: String?,
callback: MatrixCallback<Unit>): Cancelable
suspend fun agreeToTerms(serviceType: ServiceType,
baseUrl: String,
agreedUrls: List<String>,
token: String?)
}

View File

@ -31,6 +31,7 @@ import org.matrix.android.sdk.internal.extensions.toUnsignedInt
import org.matrix.olm.OlmSAS
import org.matrix.olm.OlmUtility
import timber.log.Timber
import java.util.Locale
/**
* 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? {
if ("sha256".toLowerCase() == accepted?.hash?.toLowerCase()) {
if ("sha256" == accepted?.hash?.toLowerCase(Locale.ROOT)) {
val olmUtil = OlmUtility()
val hashBytes = olmUtil.sha256(toHash)
olmUtil.releaseUtility()
@ -354,12 +355,11 @@ internal abstract class SASDefaultVerificationTransaction(
}
private fun macUsingAgreedMethod(message: String, info: String): String? {
if (SAS_MAC_SHA256_LONGKDF.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
return getSAS().calculateMacLongKdf(message, info)
} else if (SAS_MAC_SHA256.toLowerCase() == accepted?.messageAuthenticationCode?.toLowerCase()) {
return getSAS().calculateMac(message, info)
return when (accepted?.messageAuthenticationCode?.toLowerCase(Locale.ROOT)) {
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
else -> null
}
return null
}
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
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.util.Cancelable
import javax.inject.Inject
internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService {
@ -44,20 +42,20 @@ internal class DefaultIntegrationManagerService @Inject constructor(private val
return integrationManager.isIntegrationEnabled()
}
override fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
return integrationManager.setIntegrationEnabled(enable, callback)
override suspend fun setIntegrationEnabled(enable: Boolean) {
integrationManager.setIntegrationEnabled(enable)
}
override fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
return integrationManager.setWidgetAllowed(stateEventId, allowed, callback)
override suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) {
integrationManager.setWidgetAllowed(stateEventId, allowed)
}
override fun isWidgetAllowed(stateEventId: String): Boolean {
return integrationManager.isWidgetAllowed(stateEventId)
}
override fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback<Unit>): Cancelable {
return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback)
override suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) {
integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed)
}
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.LifecycleRegistry
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.session.events.model.toModel
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.widgets.model.WidgetContent
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.di.SessionDatabase
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.extractWidgetSequence
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import timber.log.Timber
import javax.inject.Inject
@ -137,22 +133,17 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
return integrationProvisioningContent?.enabled ?: false
}
fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback<Unit>): Cancelable {
suspend fun setIntegrationEnabled(enable: Boolean) {
val isIntegrationEnabled = isIntegrationEnabled()
if (enable == isIntegrationEnabled) {
callback.onSuccess(Unit)
return NoOpCancellable
return
}
val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable)
val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent)
return updateUserAccountDataTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
return updateUserAccountDataTask.execute(params)
}
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 currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
val newContent = if (currentContent == null) {
@ -165,11 +156,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
currentContent.copy(widgets = allowedWidgets)
}
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
return updateUserAccountDataTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
return updateUserAccountDataTask.execute(params)
}
fun isWidgetAllowed(stateEventId: String): Boolean {
@ -178,7 +165,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
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 currentContent = currentAllowedWidgets?.content?.toModel<AllowedWidgetsContent>()
val newContent = if (currentContent == null) {
@ -195,11 +182,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
currentContent.copy(native = nativeAllowedWidgets)
}
val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent)
return updateUserAccountDataTask
.configureWith(params) {
this.callback = callback
}
.executeBy(taskExecutor)
return updateUserAccountDataTask.execute(params)
}
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.events.model.EventType
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.members.MembershipService
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 readService: ReadService,
private val typingService: TypingService,
private val aliasService: AliasService,
private val tagsService: TagsService,
private val cryptoService: CryptoService,
private val relationService: RelationService,
@ -76,6 +78,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
RoomCallService by roomCallService,
ReadService by readService,
TypingService by typingService,
AliasService by aliasService,
TagsService by tagsService,
RelationService by relationService,
MembershipService by roomMembersService,

View File

@ -18,19 +18,25 @@ package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.MatrixCallback
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.PublicRoomsResponse
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
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.GetRoomDirectoryVisibilityTask
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.configureWith
import javax.inject.Inject
internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService {
internal class DefaultRoomDirectoryService @Inject constructor(
private val getPublicRoomTask: GetPublicRoomTask,
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask,
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask,
private val taskExecutor: TaskExecutor) : RoomDirectoryService {
override fun getPublicRooms(server: String?,
publicRoomsParams: PublicRoomsParams,
@ -49,4 +55,12 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu
}
.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.model.RoomMemberSummaryEntityFields
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.create.CreateRoomTask
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 updateBreadcrumbsTask: UpdateBreadcrumbsTask,
private val roomIdByAliasTask: GetRoomIdByAliasTask,
private val deleteRoomAliasTask: DeleteRoomAliasTask,
private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@ -125,6 +127,10 @@ internal class DefaultRoomService @Inject constructor(
.executeBy(taskExecutor)
}
override suspend fun deleteRoomAlias(roomAlias: String) {
deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias))
}
override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> {
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.util.JsonDict
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 org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse
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.JoinRoomResponse
@ -321,20 +320,11 @@ internal interface RoomAPI {
@Body body: ReportContentBody): Call<Unit>
/**
* Get the room ID associated to the room alias.
*
* @param roomAlias 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
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription>
/**
* 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>
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases")
fun getAliases(@Path("roomId") roomId: String): Call<GetAliasesResponse>
/**
* 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.room.Room
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.draft.DefaultDraftService
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 readServiceFactory: DefaultReadService.Factory,
private val typingServiceFactory: DefaultTypingService.Factory,
private val aliasServiceFactory: DefaultAliasService.Factory,
private val tagsServiceFactory: DefaultTagsService.Factory,
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
@ -76,6 +78,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomCallService = roomCallServiceFactory.create(roomId),
readService = readServiceFactory.create(roomId),
typingService = typingServiceFactory.create(roomId),
aliasService = aliasServiceFactory.create(roomId),
tagsService = tagsServiceFactory.create(roomId),
cryptoService = cryptoService,
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.internal.session.DefaultFileService
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.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.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.GetRoomLocalAliasesTask
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.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.DefaultSetRoomDirectoryVisibilityTask
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.SetRoomDirectoryVisibilityTask
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.admin.DefaultMembershipAdminTask
@ -90,6 +99,13 @@ internal abstract class RoomModule {
return retrofit.create(RoomAPI::class.java)
}
@Provides
@JvmStatic
@SessionScope
fun providesDirectoryAPI(retrofit: Retrofit): DirectoryAPI {
return retrofit.create(DirectoryAPI::class.java)
}
@Provides
@JvmStatic
fun providesParser(): Parser {
@ -127,6 +143,12 @@ internal abstract class RoomModule {
@Binds
abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask
@Binds
abstract fun bindGetRoomDirectoryVisibilityTask(task: DefaultGetRoomDirectoryVisibilityTask): GetRoomDirectoryVisibilityTask
@Binds
abstract fun bindSetRoomDirectoryVisibilityTask(task: DefaultSetRoomDirectoryVisibilityTask): SetRoomDirectoryVisibilityTask
@Binds
abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask
@ -181,9 +203,15 @@ internal abstract class RoomModule {
@Binds
abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask
@Binds
abstract fun bindGetRoomLocalAliasesTask(task: DefaultGetRoomLocalAliasesTask): GetRoomLocalAliasesTask
@Binds
abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask
@Binds
abstract fun bindDeleteRoomAliasTask(task: DefaultDeleteRoomAliasTask): DeleteRoomAliasTask
@Binds
abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask

View File

@ -16,28 +16,38 @@
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.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
internal interface AddRoomAliasTask : Task<AddRoomAliasTask.Params, Unit> {
data class Params(
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(
private val roomAPI: RoomAPI,
@UserId private val userId: String,
private val directoryAPI: DirectoryAPI,
private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
private val eventBus: EventBus
) : AddRoomAliasTask {
override suspend fun execute(params: AddRoomAliasTask.Params) {
aliasAvailabilityChecker.check(params.aliasLocalPart)
executeRequest<Unit>(eventBus) {
apiCall = roomAPI.addRoomAlias(
roomAlias = params.roomAlias,
apiCall = directoryAPI.addRoomAlias(
roomAlias = params.aliasLocalPart.toFullLocalAlias(userId),
body = AddRoomAliasBody(
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.di.SessionDatabase
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 javax.inject.Inject
@ -38,7 +38,7 @@ internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Opti
internal class DefaultGetRoomIdByAliasTask @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val roomAPI: RoomAPI,
private val directoryAPI: DirectoryAPI,
private val eventBus: EventBus
) : GetRoomIdByAliasTask {
@ -53,7 +53,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
} else {
roomId = tryOrNull("## Failed to get roomId from alias") {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
}
}?.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.matrix.android.sdk.api.failure.Failure
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.model.create.CreateRoomParams
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.query.where
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.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.user.accountdata.DirectChatsHelper
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(
private val roomAPI: RoomAPI,
@UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy,
private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker,
private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val readMarkersTask: SetReadMarkersTask,
@ -65,28 +65,11 @@ internal class DefaultCreateRoomTask @Inject constructor(
} else null
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 {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = roomAPI.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
}
aliasAvailabilityChecker.check(params.roomAliasName)
} catch (aliasError: RoomAliasError) {
throw CreateRoomFailure.AliasError(aliasError)
}
?.let {
// Alias already exists: error case
throw CreateRoomFailure.RoomAliasError.AliasNotAvailable
}
}
val createRoomBody = createRoomBodyBuilder.build(params)
@ -104,7 +87,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
} else if (throwable.httpCode == 400
&& throwable.error.code == MatrixError.M_UNKNOWN
&& throwable.error.message == "Invalid characters in room alias") {
throw CreateRoomFailure.RoomAliasError.AliasInvalid
throw CreateRoomFailure.AliasError(RoomAliasError.AliasInvalid)
}
}
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.toModel
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.RoomNameContent
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
@ -71,12 +70,6 @@ internal class RoomDisplayNameResolver @Inject constructor(
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 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.session.events.model.Event
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.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.util.Cancelable
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 {
return addRoomAliasTask
.configureWith(AddRoomAliasTask.Params(roomId, roomAlias)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun updateCanonicalAlias(alias: String, callback: MatrixCallback<Unit>): Cancelable {
override fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable {
return sendStateEvent(
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,
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 {
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) {
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.AssistedInject
import org.matrix.android.sdk.api.MatrixCallback
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.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(
@Assisted private val roomId: String,
private val taskExecutor: TaskExecutor,
private val getUploadsTask: GetUploadsTask,
private val cryptoService: CryptoService
) : UploadsService {
@ -38,11 +33,7 @@ internal class DefaultUploadsService @AssistedInject constructor(
fun create(roomId: String): UploadsService
}
override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback<GetUploadsResult>): Cancelable {
return getUploadsTask
.configureWith(GetUploadsTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), numberOfEvents, since)) {
this.callback = callback
}
.executeBy(taskExecutor)
override suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult {
return getUploadsTask.execute(GetUploadsTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), numberOfEvents, since))
}
}

View File

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

View File

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

View File

@ -246,7 +246,7 @@
<plurals name="notice_room_aliases_removed">
<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 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_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_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>

View File

@ -43,8 +43,8 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:1.3.0-beta01"
implementation 'androidx.exifinterface:exifinterface:1.3.0'
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
implementation 'androidx.exifinterface:exifinterface:1.3.1'
// Log
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.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment:$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.core:core-ktx:1.3.2'
@ -362,11 +361,11 @@ dependencies {
implementation "io.arrow-kt:arrow-core:$arrow_version"
// Pref
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.preference:preference-ktx:1.1.1'
// UI
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 "io.noties.markwon:core:$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 'com.google.android:flexbox:1.1.1'
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
implementation 'androidx.browser:browser:1.2.0'
@ -418,7 +417,7 @@ dependencies {
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
// 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-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'

View File

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

View File

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

View File

@ -41,14 +41,4 @@
</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>

View File

@ -18,7 +18,7 @@ package im.vector.app.gplay.features.settings.troubleshoot
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
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.core.resources.StringProvider
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>) {
status = TestStatus.RUNNING
try {
FirebaseInstanceId.getInstance().instanceId
FirebaseMessaging.getInstance().token
.addOnCompleteListener(context) { task ->
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)
if ("SERVICE_NOT_AVAILABLE".equals(errorMsg)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg)
} else if ("TOO_MANY_REGISTRATIONS".equals(errorMsg)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_too_many_registration, errorMsg)
} else if ("ACCOUNT_MISSING".equals(errorMsg)) {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_fcm_failed_account_missing_quick_fix) {
override fun doFix() {
startAddGoogleAccountIntent(context, activityResultLauncher)
}
description = when (val errorMsg = task.exception?.localizedMessage ?: "Unknown") {
"SERVICE_NOT_AVAILABLE" -> {
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_service_not_available, errorMsg)
}
"TOO_MANY_REGISTRATIONS" -> {
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) {
override fun doFix() {
startAddGoogleAccountIntent(context, activityResultLauncher)
}
}
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed_account_missing, errorMsg)
}
else -> {
stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg)
}
} else {
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_failed, errorMsg)
}
status = TestStatus.FAILED
} else {
task.result?.token?.let { token ->
val tok = token.substring(0, Math.min(8, token.length)) + "********************"
task.result?.let { token ->
val tok = token.take(8) + "********************"
description = stringProvider.getString(R.string.settings_troubleshoot_test_fcm_success, tok)
Timber.e("Retrieved FCM token success [$tok].")
// 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 com.google.android.gms.common.ConnectionResult
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.core.di.ActiveSessionHolder
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'
if (checkPlayServices(activity)) {
try {
FirebaseInstanceId.getInstance().instanceId
.addOnSuccessListener(activity) { instanceIdResult ->
storeFcmToken(activity, instanceIdResult.token)
FirebaseMessaging.getInstance().token
.addOnSuccessListener { token ->
storeFcmToken(activity, token)
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) {
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.BootstrapSetupRecoveryKeyFragment
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.VerificationNotMeFragment
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.members.RoomMemberListFragment
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.files.RoomUploadsFilesFragment
import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment
@ -363,6 +365,11 @@ interface FragmentModule {
@FragmentKey(RoomSettingsFragment::class)
fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomAliasFragment::class)
fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment
@Binds
@IntoMap
@FragmentKey(RoomMemberProfileFragment::class)
@ -418,6 +425,11 @@ interface FragmentModule {
@FragmentKey(VerificationCancelFragment::class)
fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment
@Binds
@IntoMap
@FragmentKey(QuadSLoadingFragment::class)
fun bindQuadSLoadingFragment(fragment: QuadSLoadingFragment): Fragment
@Binds
@IntoMap
@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.devices.DeviceListBottomSheet
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.devices.DeviceVerificationInfoBottomSheet
import im.vector.app.features.share.IncomingShareActivity
@ -153,6 +156,9 @@ interface ScreenComponent {
fun inject(bottomSheet: ViewEditHistoryBottomSheet)
fun inject(bottomSheet: DisplayReadReceiptsBottomSheet)
fun inject(bottomSheet: RoomListQuickActionsBottomSheet)
fun inject(bottomSheet: RoomAliasBottomSheet)
fun inject(bottomSheet: RoomHistoryVisibilityBottomSheet)
fun inject(bottomSheet: RoomJoinRuleBottomSheet)
fun inject(bottomSheet: VerificationBottomSheet)
fun inject(bottomSheet: DeviceVerificationInfoBottomSheet)
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.roomdirectory.RoomDirectorySharedActionViewModel
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
@Module
@ -105,6 +108,21 @@ interface ViewModelModule {
@ViewModelKey(RoomListQuickActionsSharedActionViewModel::class)
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
@IntoMap
@ViewModelKey(RoomDirectorySharedActionViewModel::class)

View File

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

View File

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

View File

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

View File

@ -57,3 +57,15 @@ fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_searc
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 im.vector.app.core.utils.getFileExtension
import timber.log.Timber
import java.util.Locale
/**
* Returns the mimetype from a uri.
@ -44,7 +45,7 @@ fun getMimeTypeFromUri(context: Context, uri: Uri): String? {
if (null != mimeType) {
// the mimetype is sometimes in uppercase.
mimeType = mimeType.toLowerCase()
mimeType = mimeType.toLowerCase(Locale.ROOT)
}
} catch (e: Exception) {
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
*/
// 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>> {
setState { stateReducer(Loading()) }
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
*/
// 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>> {
setState { stateReducer(Loading()) }
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) {
behaviorRelay.accept(value)
behaviorRelay.accept(value!!)
}
private fun createRelay(): BehaviorRelay<T> {
@ -68,6 +68,6 @@ open class PublishDataSource<T> : MutableDataSource<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 timber.log.Timber
import java.io.File
import java.util.Locale
// Implementation should return true in case of success
typealias ActionOnFile = (file: File) -> Boolean
@ -113,7 +114,7 @@ fun getFileExtension(fileUri: String): String? {
val ext = filename.substring(dotPos + 1)
if (ext.isNotBlank()) {
return ext.toLowerCase()
return ext.toLowerCase(Locale.ROOT)
}
}
}

View File

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

View File

@ -17,31 +17,12 @@
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.platform.VectorViewModel
class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialState: AttachmentsPreviewViewState)
class AttachmentsPreviewViewModel(initialState: AttachmentsPreviewViewState)
: 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) {
when (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 VerifyFromPassphrase : VerificationAction()
data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction()
object CancelledFromSsss : VerificationAction()
object SecuredStorageHasBeenReset : VerificationAction()
}

View File

@ -155,6 +155,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
// all have been reset, so we are verified?
viewModel.handle(VerificationAction.SecuredStorageHasBeenReset)
}
} else {
viewModel.handle(VerificationAction.CancelledFromSsss)
}
}
@ -209,6 +211,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() {
return@withState
}
if (state.selfVerificationMode && state.verifyingFrom4S) {
showFragment(QuadSLoadingFragment::class, Bundle())
return@withState
}
if (state.selfVerificationMode && state.verifiedFromPrivateKeys) {
showFragment(VerificationConclusionFragment::class, Bundle().apply {
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.resources.StringProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
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)
val selfVerificationMode: Boolean = false,
val verifiedFromPrivateKeys: Boolean = false,
val verifyingFrom4S: Boolean = false,
val isMe: Boolean = false,
val currentDeviceCanCrossSign: Boolean = false,
val userWantsToCancel: Boolean = false,
@ -170,7 +172,9 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
}
} else {
// 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
} else {
setState {
@ -346,6 +350,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
_viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
}
is VerificationAction.VerifyFromPassphrase -> {
setState { copy(verifyingFrom4S = true) }
_viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore)
}
is VerificationAction.GotResultFromSsss -> {
@ -354,56 +359,73 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
VerificationAction.SecuredStorageHasBeenReset -> {
if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) {
setState {
copy(quadSHasBeenReset = true)
copy(quadSHasBeenReset = true, verifyingFrom4S = false)
}
}
Unit
}
VerificationAction.CancelledFromSsss -> {
setState {
copy(verifyingFrom4S = false)
}
}
}.exhaustive
}
private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) {
try {
action.cypherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<Map<String, String>>(ins, action.alias)
val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys(
res?.get(MASTER_KEY_SSSS_NAME),
res?.get(USER_SIGNING_KEY_SSSS_NAME),
res?.get(SELF_SIGNING_KEY_SSSS_NAME)
)
if (trustResult.isVerified()) {
// Sign this device and upload the signature
session.sessionParams.deviceId?.let { deviceId ->
session.cryptoService()
.crossSigningService().trustDevice(deviceId, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
Timber.w(failure, "Failed to sign my device after recovery")
}
})
}
viewModelScope.launch(Dispatchers.IO) {
try {
action.cypherData.fromBase64().inputStream().use { ins ->
val res = session.loadSecureSecret<Map<String, String>>(ins, action.alias)
val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys(
res?.get(MASTER_KEY_SSSS_NAME),
res?.get(USER_SIGNING_KEY_SSSS_NAME),
res?.get(SELF_SIGNING_KEY_SSSS_NAME)
)
if (trustResult.isVerified()) {
// Sign this device and upload the signature
session.sessionParams.deviceId?.let { deviceId ->
session.cryptoService()
.crossSigningService().trustDevice(deviceId, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
Timber.w(failure, "Failed to sign my device after recovery")
}
})
}
setState {
copy(verifiedFromPrivateKeys = true)
}
setState {
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
tentativeRestoreBackup(res)
} else {
setState {
copy(
verifyingFrom4S = false
)
}
// POP UP something
_viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys)))
}
}
// try the keybackup
tentativeRestoreBackup(res)
Unit
} catch (failure: Throwable) {
setState {
copy(
verifyingFrom4S = false
)
}
_viewEvents.post(
VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error)))
}
} catch (failure: Throwable) {
_viewEvents.post(
VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error)))
}
}
private fun tentativeRestoreBackup(res: Map<String, String>?) {
viewModelScope.launch(Dispatchers.IO) {
GlobalScope.launch(Dispatchers.IO) {
try {
val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also {
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)
abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinueCancelItem.Holder>() {
@EpoxyAttribute
var continueText: String? = null
@EpoxyAttribute
var continueOnClick: ClickListener? = null
@ -37,6 +40,8 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder<SettingsContinu
super.bind(holder)
holder.cancelButton.onClick(cancelOnClick)
continueText?.let { holder.continueButton.text = it }
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.session.Session
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.internal.util.awaitCallback
import java.net.UnknownHostException
@ -117,9 +116,7 @@ class SetIdentityServerViewModel @AssistedInject constructor(
private suspend fun checkTerms(baseUrl: String) {
try {
val data = awaitCallback<GetTermsResponse> {
mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl, it)
}
val data = mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl)
// has all been accepted?
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.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_form_text_input)
@ -65,9 +66,7 @@ abstract class FormEditTextItem : VectorEpoxyModel<FormEditTextItem.Holder>() {
holder.textInputLayout.error = errorMessage
// Update only if text is different and value is not null
if (value != null && holder.textInputEditText.text.toString() != value) {
holder.textInputEditText.setText(value)
}
holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.isEnabled = enabled
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.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_form_text_input_with_button)
@ -61,9 +62,7 @@ abstract class FormEditTextWithButtonItem : VectorEpoxyModel<FormEditTextWithBut
holder.textInputLayout.hint = hint
// Update only if text is different
if (holder.textInputEditText.text.toString() != value) {
holder.textInputEditText.setText(value)
}
holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.isEnabled = enabled
holder.textInputEditText.addTextChangedListener(onTextChangeListener)

View File

@ -61,8 +61,8 @@ abstract class FormSwitchItem : VectorEpoxyModel<FormSwitchItem.Holder>() {
holder.switchView.isEnabled = enabled
holder.switchView.setOnCheckedChangeListener(null)
holder.switchView.isChecked = switchChecked
holder.switchView.setOnCheckedChangeListener { _, 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_MEMBER,
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STATE_ROOM_ALIASES,
EventType.STATE_ROOM_CANONICAL_ALIAS,
EventType.STATE_ROOM_JOIN_RULES,
EventType.STATE_ROOM_HISTORY_VISIBILITY,
@ -79,6 +78,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
encryptedItemFactory.create(event, nextEvent, highlight, callback)
}
}
EventType.STATE_ROOM_ALIASES,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_KEY,

View File

@ -260,13 +260,13 @@ class NoticeEventFormatter @Inject constructor(
private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
val formattedVisibility = roomHistoryVisibilityFormatter.format(historyVisibility)
val historyVisibilitySuffix = roomHistoryVisibilityFormatter.getNoticeSuffix(historyVisibility)
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,
formattedVisibility)
historyVisibilitySuffix)
} else {
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,21 +465,72 @@ class NoticeEventFormatter @Inject constructor(
private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? {
val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel()
val canonicalAlias = eventContent?.canonicalAlias
return canonicalAlias
?.takeIf { it.isNotBlank() }
?.let {
val prevContent: RoomCanonicalAliasContent? = event.resolvedPrevContent().toModel()
val canonicalAlias = eventContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
val prevCanonicalAlias = prevContent?.canonicalAlias?.takeIf { it.isNotEmpty() }
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()) {
sp.getString(R.string.notice_room_canonical_alias_no_change_by_you)
} else {
sp.getString(R.string.notice_room_canonical_alias_no_change, senderName)
}
}
added.isEmpty() && removed.isEmpty() -> {
// Canonical has changed
if (canonicalAlias != null) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_set_by_you, it)
sp.getString(R.string.notice_room_canonical_alias_set_by_you, canonicalAlias)
} else {
sp.getString(R.string.notice_room_canonical_alias_set, senderName, it)
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)
} else {
sp.getString(R.string.notice_room_canonical_alias_unset, senderName)
}
}
?: if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_room_canonical_alias_unset_by_you)
}
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.getString(R.string.notice_room_canonical_alias_unset, senderName)
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? {

View File

@ -24,13 +24,21 @@ import javax.inject.Inject
class RoomHistoryVisibilityFormatter @Inject constructor(
private val stringProvider: StringProvider
) {
fun getNoticeSuffix(roomHistoryVisibility: RoomHistoryVisibility): String {
return stringProvider.getString(when (roomHistoryVisibility) {
RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable
RoomHistoryVisibility.SHARED -> R.string.notice_room_visibility_shared
RoomHistoryVisibility.INVITED -> R.string.notice_room_visibility_invited
RoomHistoryVisibility.JOINED -> R.string.notice_room_visibility_joined
})
}
fun format(roomHistoryVisibility: RoomHistoryVisibility): String {
return when (roomHistoryVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited)
RoomHistoryVisibility.JOINED -> stringProvider.getString(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() {
recyclerView.cleanup()
roomListActionsEpoxyController.listener = null
super.onDestroyView()
}

View File

@ -56,6 +56,7 @@ class PinFragment @Inject constructor(
when (fragmentArgs.pinMode) {
PinMode.CREATE -> showCreateFragment()
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()
}
override fun onPinCodeEnteredFirst(pinCode: String?): Boolean {
return false
}
override fun onCodeCreated(encodedCode: String) {
lifecycleScope.launch {
pinCodeStore.storeEncodedPin(encodedCode)

View File

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

View File

@ -20,7 +20,6 @@ import com.airbnb.epoxy.TypedEpoxyController
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.discovery.settingsSectionTitleItem
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 javax.inject.Inject
class CreateRoomController @Inject constructor(private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter
class CreateRoomController @Inject constructor(
private val stringProvider: StringProvider,
private val roomAliasErrorFormatter: RoomAliasErrorFormatter
) : TypedEpoxyController<CreateRoomViewState>() {
var listener: Listener? = null
@ -104,13 +104,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
value(viewState.roomType.aliasLocalPart)
homeServer(":" + viewState.homeServerName)
errorMessage(
when ((viewState.asyncCreateRoomRequest as? Fail)?.error) {
is CreateRoomFailure.RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty
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) }
roomAliasErrorFormatter.format(
(((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError)
)
onTextChange { value ->
listener?.setAliasLocalPart(value)

View File

@ -84,7 +84,7 @@ class CreateRoomFragment @Inject constructor(
override fun showFailure(throwable: Throwable) {
// Note: RoomAliasError are displayed directly in the form
if (throwable !is CreateRoomFailure.RoomAliasError) {
if (throwable !is CreateRoomFailure.AliasError) {
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.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.extensions.setTextSafe
import im.vector.app.core.platform.SimpleTextWatcher
@EpoxyModelClass(layout = R.layout.item_room_alias_text_input)
@ -62,9 +63,7 @@ abstract class RoomAliasEditItem : VectorEpoxyModel<RoomAliasEditItem.Holder>()
holder.textInputLayout.error = errorMessage
// Update only if text is different and value is not null
if (value != null && holder.textInputEditText.text.toString() != value) {
holder.textInputEditText.setText(value)
}
holder.textInputEditText.setTextSafe(value)
holder.textInputEditText.isEnabled = enabled
holder.textInputEditText.addTextChangedListener(onTextChangeListener)
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.members.RoomMemberListFragment
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 javax.inject.Inject
@ -98,10 +99,11 @@ class RoomProfileActivity :
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers()
is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings()
is RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias()
is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads()
is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers()
}
}
.disposeOnDestroy()
@ -135,6 +137,10 @@ class RoomProfileActivity :
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs)
}
private fun openRoomAlias() {
addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs)
}
private fun openRoomMembers() {
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 {
object OpenRoomSettings : RoomProfileSharedAction()
object OpenRoomAliasesSettings : RoomProfileSharedAction()
object OpenRoomUploads : RoomProfileSharedAction()
object OpenRoomMembers : 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