diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index d13e40248f..5ad39614b7 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -31,6 +31,7 @@ ssss sygnal threepid + unpublish unwedging diff --git a/CHANGES.md b/CHANGES.md index 7f626da497..1005040328 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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) =================================================== diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 91ddd519df..59ba6c4500 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -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' diff --git a/build.gradle b/build.gradle index 0c4b35b060..6dd61a720c 100644 --- a/build.gradle +++ b/build.gradle @@ -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' diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle index 3d62758065..37f41d0a2a 100644 --- a/matrix-sdk-android-rx/build.gradle +++ b/matrix-sdk-android-rx/build.gradle @@ -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" diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt index f6dbe3d160..ec30a31f6d 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt @@ -21,34 +21,36 @@ import org.matrix.android.sdk.api.util.Cancelable import io.reactivex.Completable import io.reactivex.Single -fun singleBuilder(builder: (callback: MatrixCallback) -> Cancelable): Single = Single.create { - val callback: MatrixCallback = object : MatrixCallback { +fun singleBuilder(builder: (MatrixCallback) -> Cancelable): Single = Single.create { emitter -> + val callback = object : MatrixCallback { 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 completableBuilder(builder: (callback: MatrixCallback) -> Cancelable): Completable = Completable.create { - val callback: MatrixCallback = object : MatrixCallback { +fun completableBuilder(builder: (MatrixCallback) -> Cancelable): Completable = Completable.create { emitter -> + val callback = object : MatrixCallback { 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() } } diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index 86f2d26808..bf4bcacc31 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -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 { - room.addRoomAlias(alias, it) - } - - fun updateCanonicalAlias(alias: String): Completable = completableBuilder { - room.updateCanonicalAlias(alias, it) - } - fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder { room.updateHistoryReadability(readability, it) } + fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = completableBuilder { + room.updateJoinRule(joinRules, guestAccess, it) + } + fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder { room.updateAvatar(avatarUri, fileName, it) } diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 29c709844a..d961560c17 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -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' diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 0a7f3ff09f..68874a1fb1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -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" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt index e27d81edb7..60af93888e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt @@ -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): 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): 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): Cancelable + suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) /** * Returns true if the widget domain is allowed, false otherwise. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 837bda031b..cb6690b5c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -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, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt index dc5b3d55f5..61970ce848 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt @@ -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>): 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) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index f30037e5c2..477bef66cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -122,6 +122,11 @@ interface RoomService { searchOnServer: Boolean, callback: MatrixCallback>): 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt new file mode 100644 index 0000000000..5fe7e99425 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt @@ -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 + + /** + * 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) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt new file mode 100644 index 0000000000..d2cb7c58a9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt @@ -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() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt index b4e2dc645c..208cdd4556 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt @@ -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() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt index f70e013786..59989f3045 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt @@ -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( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt index 5487b2ff82..4e8bd2e71b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt @@ -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? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index e4baa58c30..74e3faf38a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -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): Cancelable - /** - * Add new alias to the room. - */ - fun addRoomAlias(roomAlias: String, callback: MatrixCallback): 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): Cancelable + fun updateCanonicalAlias(alias: String?, altAliases: List, callback: MatrixCallback): Cancelable /** * Update the history readability of the room */ fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback): Cancelable + /** + * Update the join rule and/or the guest access + */ + fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback): Cancelable + /** * Update the avatar of the room */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt index c3cc1eb9ee..e2462d007d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt @@ -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): Cancelable + suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt index 2d88125662..10ce0829d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt @@ -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): Cancelable + suspend fun getTerms(serviceType: ServiceType, baseUrl: String): GetTermsResponse - fun agreeToTerms(serviceType: ServiceType, - baseUrl: String, - agreedUrls: List, - token: String?, - callback: MatrixCallback): Cancelable + suspend fun agreeToTerms(serviceType: ServiceType, + baseUrl: String, + agreedUrls: List, + token: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 22a190c68e..c7885ce449 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -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 { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt new file mode 100644 index 0000000000..6a50f3ee37 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -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 + + /** + * 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 + + /** + * 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 + + /** + * 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 + + /** + * 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 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt new file mode 100644 index 0000000000..ddf927a3dc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt @@ -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 +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt index 753e865b4a..8bf6437009 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt @@ -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): Cancelable { - return integrationManager.setIntegrationEnabled(enable, callback) + override suspend fun setIntegrationEnabled(enable: Boolean) { + integrationManager.setIntegrationEnabled(enable) } - override fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback): 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): 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 { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt index df4e407415..ebd57ce657 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt @@ -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): 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): Cancelable { + suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) { val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS) val currentContent = currentAllowedWidgets?.content?.toModel() 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): Cancelable { + suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) { val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS) val currentContent = currentAllowedWidgets?.content?.toModel() 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 { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index c7bb640f7c..7a819250cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -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, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt index a091b5f85e..0d41c6f35e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt @@ -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)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 28656463c1..9ec985e0b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -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> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index fc80842f73..955a251b52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -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 /** - * 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 - - /** - * 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 + @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases") + fun getAliases(@Path("roomId") roomId: String): Call /** * Inform that the user is starting to type or has stopped typing diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index d4fa040d06..63370a1ad8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -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), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 6381796ee0..3a94396a61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt index 695be3f633..9793750fa0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt @@ -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 { 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(eventBus) { - apiCall = roomAPI.addRoomAlias( - roomAlias = params.roomAlias, + apiCall = directoryAPI.addRoomAlias( + roomAlias = params.aliasLocalPart.toFullLocalAlias(userId), body = AddRoomAliasBody( roomId = params.roomId ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt new file mode 100644 index 0000000000..b6c69224e6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt @@ -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 { + return getRoomLocalAliasesTask.execute(GetRoomLocalAliasesTask.Params(roomId)) + } + + override suspend fun addAlias(aliasLocalPart: String) { + addRoomAliasTask.execute(AddRoomAliasTask.Params(roomId, aliasLocalPart)) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt new file mode 100644 index 0000000000..3400fd994c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt @@ -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 { + 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(eventBus) { + apiCall = directoryAPI.deleteRoomAlias( + roomAlias = params.roomAlias + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt new file mode 100644 index 0000000000..5965924085 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt @@ -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 = emptyList() +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index 58a119cc77..3c47ee6ef0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -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(eventBus) { - apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) + apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias) } }?.roomId Optional.from(roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt new file mode 100644 index 0000000000..7cfce4ecdc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt @@ -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> { + 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 { + // We do not check for "org.matrix.msc2432", so the API may be missing + val response = executeRequest(eventBus) { + apiCall = roomAPI.getAliases(roomId = params.roomId) + } + + return response.aliases + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt new file mode 100644 index 0000000000..25ba493891 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -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(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(":") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 0fe9b0ba68..ef792ab98e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -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 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(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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt new file mode 100644 index 0000000000..fbdd6a03eb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt @@ -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 { + 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(eventBus) { + apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId) + } + .visibility + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt new file mode 100644 index 0000000000..33b12aa1ca --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt @@ -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 { + 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(eventBus) { + apiCall = directoryAPI.setRoomDirectoryVisibility( + params.roomId, + RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility) + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index a7dfcfc96f..784b610af7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -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()?.aliases?.firstOrNull() - if (!name.isNullOrEmpty()) { - return name - } - val roomMembers = RoomMemberHelper(realm, roomId) val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 3463b26c8a..6015d945c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -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): Cancelable { - return addRoomAliasTask - .configureWith(AddRoomAliasTask.Params(roomId, roomAlias)) { - this.callback = callback - } - .executeBy(taskExecutor) - } - - override fun updateCanonicalAlias(alias: String, callback: MatrixCallback): Cancelable { + override fun updateCanonicalAlias(alias: String?, altAliases: List, callback: MatrixCallback): 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): Cancelable { + return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + if (joinRules != null) { + awaitCallback { + sendStateEvent( + eventType = EventType.STATE_ROOM_JOIN_RULES, + body = RoomJoinRulesContent(joinRules).toContent(), + callback = it, + stateKey = null + ) + } + } + if (guestAccess != null) { + awaitCallback { + sendStateEvent( + eventType = EventType.STATE_ROOM_GUEST_ACCESS, + body = RoomGuestAccessContent(guestAccess).toContent(), + callback = it, + stateKey = null + ) + } + } + } + } + override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt index 824bd23c01..895f1cf50d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt @@ -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): 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)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 5eb97cee3a..41914cc799 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -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): 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(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, - token: String?, - callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + override suspend fun agreeToTerms(serviceType: TermsService.ServiceType, + baseUrl: String, + agreedUrls: List, + token: String?) { + withContext(coroutineDispatchers.main) { val url = buildUrl(baseUrl, serviceType) val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt index 3d80ad01d5..e19b1bcca7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Hash.kt @@ -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() diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index f77cd3203d..7a0fe1d735 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -246,7 +246,7 @@ %1$s removed %2$s as an address for this room. - %1$s removed %3$s as addresses for this room. + %1$s removed %2$s as addresses for this room. @@ -262,6 +262,33 @@ "%1$s removed the main address for this room." "You removed the main address for this room." + + %1$s added the alternative address %2$s for this room. + %1$s added the alternative addresses %2$s for this room. + + + + You added the alternative address %1$s for this room. + You added the alternative addresses %1$s for this room. + + + + %1$s removed the alternative address %2$s for this room. + %1$s removed the alternative addresses %2$s for this room. + + + + You removed the alternative address %1$s for this room. + You removed the alternative addresses %1$s for this room. + + + %1$s changed the alternative addresses for this room. + You changed the alternative addresses for this room. + %1$s changed the main and alternative addresses for this room. + You changed the main and alternative addresses for this room. + %1$s changed the addresses for this room. + You changed the addresses for this room. + "%1$s has allowed guests to join the room." "You have allowed guests to join the room." "%1$s has allowed guests to join here." diff --git a/multipicker/build.gradle b/multipicker/build.gradle index b6e500e493..7c29a5539f 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -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' diff --git a/vector/build.gradle b/vector/build.gradle index 561e1fd824..62ff3951af 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -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' diff --git a/vector/lint.xml b/vector/lint.xml index 4ac0f20e51..572f937406 100644 --- a/vector/lint.xml +++ b/vector/lint.xml @@ -41,6 +41,7 @@ + @@ -52,6 +53,9 @@ + + + diff --git a/vector/src/debug/res/layout/activity_test_linkify.xml b/vector/src/debug/res/layout/activity_test_linkify.xml index bbaadb20a2..7e625ad08c 100644 --- a/vector/src/debug/res/layout/activity_test_linkify.xml +++ b/vector/src/debug/res/layout/activity_test_linkify.xml @@ -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"> - - - - - - \ No newline at end of file diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt index 32888dafd7..1107737888 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestFirebaseToken.kt @@ -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) { 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 diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt index 913eab211d..f3bdcafb1c 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt @@ -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") } diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 32c98922fb..188ca32559 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 818a32fca3..f56a6a3d70 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 7ae8bc9c2e..bed2e0b850 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt index e28bec6874..80792648f6 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt @@ -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() 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 } diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt index fdbe9f7f94..99acd6cb36 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt @@ -59,9 +59,7 @@ fun EpoxyController.buildProfileAction( accessoryRes(accessory) accessoryMatrixItem(accessoryMatrixItem) avatarRenderer(avatarRenderer) - listener { _ -> - action?.invoke() - } + listener(action) } if (divider) { diff --git a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt index 05b70def3d..33e7199334 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt @@ -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) + } +} diff --git a/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt b/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt index c8a2bf65d5..1299f4086b 100644 --- a/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt +++ b/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt @@ -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") diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt index 002dfcf068..d6f43beaf7 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorViewModel.kt @@ -43,7 +43,7 @@ abstract class VectorViewModel Single.toAsync(stateReducer: S.(Async) -> S): Single> { setState { stateReducer(Loading()) } return map { Success(it) as Async } @@ -56,7 +56,7 @@ abstract class VectorViewModel Observable.toAsync(stateReducer: S.(Async) -> S): Observable> { setState { stateReducer(Loading()) } return map { Success(it) as Async } diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt new file mode 100644 index 0000000000..da136fb072 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt @@ -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 : + VectorBaseBottomSheetDialogFragment(), + BottomSheetGenericController.Listener { + + @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 + + @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() + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt new file mode 100644 index 0000000000..da48accf35 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt @@ -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) + } + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt new file mode 100644 index 0000000000..67347c3220 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt @@ -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 + : TypedEpoxyController() { + + var listener: Listener? = null + + abstract fun getTitle(): String? + + open fun getSubTitle(): String? = null + + abstract fun getActions(state: State): List + + 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 { + fun didSelectAction(action: Action) + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt new file mode 100644 index 0000000000..49147b954a --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt @@ -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 : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt new file mode 100644 index 0000000000..38c81a7ef6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt new file mode 100644 index 0000000000..6cc2c4c981 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericViewModel.kt @@ -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(initialState: State) : + VectorViewModel(initialState) { + + override fun handle(action: EmptyAction) { + // No op + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt new file mode 100644 index 0000000000..27fb634480 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt @@ -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() { + + @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(R.id.itemBottomSheetTitleTitle) + val subtitle by bind(R.id.itemBottomSheetTitleSubtitle) + } +} diff --git a/vector/src/main/java/im/vector/app/core/utils/DataSource.kt b/vector/src/main/java/im/vector/app/core/utils/DataSource.kt index 8a908ad1d4..06bdeb9277 100644 --- a/vector/src/main/java/im/vector/app/core/utils/DataSource.kt +++ b/vector/src/main/java/im/vector/app/core/utils/DataSource.kt @@ -44,7 +44,7 @@ open class BehaviorDataSource(private val defaultValue: T? = null) : MutableD } override fun post(value: T) { - behaviorRelay.accept(value) + behaviorRelay.accept(value!!) } private fun createRelay(): BehaviorRelay { @@ -68,6 +68,6 @@ open class PublishDataSource : MutableDataSource { } override fun post(value: T) { - publishRelay.accept(value) + publishRelay.accept(value!!) } } diff --git a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt index ab99ba61bd..aa36dd0959 100644 --- a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt @@ -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) } } } diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt index ba0250724c..f67b0946cc 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt index 59a0937d89..28d617e613 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewViewModel.kt @@ -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(initialState) { - @AssistedInject.Factory - interface Factory { - fun create(initialState: AttachmentsPreviewViewState): AttachmentsPreviewViewModel - } - - companion object : MvRxViewModelFactory { - - @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) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt new file mode 100644 index 0000000000..a0ab1c86a7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt @@ -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 +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt index a32a9de97f..a5142ad8bf 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 35ea96de6f..a9b76366df 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -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)) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index aa20a9a992..23ed9b6483 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -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>(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 { - 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>(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 { + 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?) { - 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") diff --git a/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt b/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt index c9ad23f1a9..b59b24fe55 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt @@ -27,6 +27,9 @@ import im.vector.app.core.epoxy.onClick @EpoxyModelClass(layout = R.layout.item_settings_continue_cancel) abstract class SettingsContinueCancelItem : EpoxyModelWithHolder() { + @EpoxyAttribute + var continueText: String? = null + @EpoxyAttribute var continueOnClick: ClickListener? = null @@ -37,6 +40,8 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder { - mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl, it) - } + val data = mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl) // has all been accepted? val resp = data.serverResponse diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index 12538d314a..68e2e6b371 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -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() { 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 } diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt index eadae3ba0c..08fc435e11 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt @@ -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() { holder.switchView.isEnabled = enabled + holder.switchView.setOnCheckedChangeListener(null) holder.switchView.isChecked = switchChecked - holder.switchView.setOnCheckedChangeListener { _, isChecked -> listener?.invoke(isChecked) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 575f28b610..243cbbd0e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -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, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c4cc2e87b0..9b828e9410 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -260,13 +260,13 @@ class NoticeEventFormatter @Inject constructor( private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { val historyVisibility = event.getClearContent().toModel()?.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? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt index 4563e6a6ed..14769bc95b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt @@ -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 + }) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index e3a5db4b97..f41104cae1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -77,6 +77,7 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R override fun onDestroyView() { recyclerView.cleanup() + roomListActionsEpoxyController.listener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index b6e238c2dc..1aa4846f38 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -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) diff --git a/vector/src/main/java/im/vector/app/features/pin/PinMode.kt b/vector/src/main/java/im/vector/app/features/pin/PinMode.kt index c24ac5adf2..9801912bd6 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinMode.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinMode.kt @@ -18,5 +18,6 @@ package im.vector.app.features.pin enum class PinMode { CREATE, - AUTH + AUTH, + MODIFY } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt index aaf7b6ead5..94b419797d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt @@ -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() { 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) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index fb90752764..204a99929b 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt index 041a5c5c51..2a30545a47 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt @@ -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() 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 diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt new file mode 100644 index 0000000000..7a23a79ab3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt @@ -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) } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 609042ffa4..696725d001 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -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) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 0052ddee99..83a610cf1b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -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() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt new file mode 100644 index 0000000000..80e1603453 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt new file mode 100644 index 0000000000..0b695031c5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -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() { + + 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() } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt new file mode 100644 index 0000000000..56c3e76828 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -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) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt new file mode 100644 index 0000000000..bbd44741b5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt @@ -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() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt new file mode 100644 index 0000000000..5873d9ce8a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -0,0 +1,384 @@ +/* + * 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 androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +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 +import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.rx.mapOptional +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap + +class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: RoomAliasViewState, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomAliasViewState): RoomAliasViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomAliasViewState): RoomAliasViewModel? { + val fragment: RoomAliasFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.viewModelFactory.create(state) + } + } + + private val room = session.getRoom(initialState.roomId)!! + + init { + initHomeServerName() + observeRoomSummary() + observePowerLevel() + observeRoomCanonicalAlias() + fetchRoomAlias() + fetchRoomDirectoryVisibility() + } + + private fun fetchRoomDirectoryVisibility() { + setState { + copy( + roomDirectoryVisibility = Loading() + ) + } + viewModelScope.launch { + runCatching { + session.getRoomDirectoryVisibility(room.roomId) + }.fold( + { + setState { + copy( + roomDirectoryVisibility = Success(it) + ) + } + }, + { + setState { + copy( + roomDirectoryVisibility = Fail(it) + ) + } + } + ) + } + } + + private fun initHomeServerName() { + setState { + copy( + homeServerName = session.myUserId.substringAfter(":") + ) + } + } + + private fun fetchRoomAlias() { + setState { + copy( + localAliases = Loading() + ) + } + + viewModelScope.launch { + runCatching { room.getRoomAliases() } + .fold( + { + setState { copy(localAliases = Success(it.sorted())) } + }, + { + setState { copy(localAliases = Fail(it)) } + } + ) + } + } + + private fun observeRoomSummary() { + room.rx().liveRoomSummary() + .unwrap() + .execute { async -> + copy( + roomSummary = async + ) + } + } + + private fun observePowerLevel() { + PowerLevelsObservableFactory(room) + .createObservable() + .subscribe { + val powerLevelsHelper = PowerLevelsHelper(it) + val permissions = RoomAliasViewState.ActionPermissions( + canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend( + userId = session.myUserId, + isState = true, + eventType = EventType.STATE_ROOM_CANONICAL_ALIAS + ) + ) + setState { + val newPublishManuallyState = if (permissions.canChangeCanonicalAlias) { + when (publishManuallyState) { + RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Closed + else -> publishManuallyState + } + } else { + RoomAliasViewState.AddAliasState.Hidden + } + copy( + actionPermissions = permissions, + publishManuallyState = newPublishManuallyState + ) + } + } + .disposeOnClear() + } + + /** + * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. + */ + private fun observeRoomCanonicalAlias() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + setState { + copy( + canonicalAlias = it.canonicalAlias, + alternativeAliases = it.alternativeAliases.orEmpty().sorted() + ) + } + } + .disposeOnClear() + } + + override fun handle(action: RoomAliasAction) { + when (action) { + RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm() + is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) + is RoomAliasAction.ManualPublishAlias -> handleManualPublishAlias() + is RoomAliasAction.UnpublishAlias -> handleUnpublishAlias(action) + is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + is RoomAliasAction.SetRoomDirectoryVisibility -> handleSetRoomDirectoryVisibility(action) + RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm() + is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) + RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + is RoomAliasAction.PublishAlias -> handlePublishAlias(action) + }.exhaustive + } + + private fun handleSetRoomDirectoryVisibility(action: RoomAliasAction.SetRoomDirectoryVisibility) { + postLoading(true) + viewModelScope.launch { + runCatching { + session.setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility) + }.fold( + { + setState { + copy( + isLoading = false, + // Local echo, no need to fetch the data from the server again + roomDirectoryVisibility = Success(action.roomDirectoryVisibility) + ) + } + }, + { + postLoading(false) + _viewEvents.post(RoomAliasViewEvents.Failure(it)) + } + ) + } + } + + private fun handleToggleAddLocalAliasForm() { + setState { + copy( + newLocalAliasState = when (newLocalAliasState) { + RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Hidden + RoomAliasViewState.AddAliasState.Closed -> RoomAliasViewState.AddAliasState.Editing("", Uninitialized) + is RoomAliasViewState.AddAliasState.Editing -> RoomAliasViewState.AddAliasState.Closed + } + ) + } + } + + private fun handleToggleManualPublishForm() { + setState { + copy( + publishManuallyState = when (publishManuallyState) { + RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Hidden + RoomAliasViewState.AddAliasState.Closed -> RoomAliasViewState.AddAliasState.Editing("", Uninitialized) + is RoomAliasViewState.AddAliasState.Editing -> RoomAliasViewState.AddAliasState.Closed + } + ) + } + } + + private fun handleSetNewAlias(action: RoomAliasAction.SetNewAlias) { + setState { + copy( + publishManuallyState = RoomAliasViewState.AddAliasState.Editing(action.alias, Uninitialized) + ) + } + } + + private fun handleSetNewLocalAliasLocalPart(action: RoomAliasAction.SetNewLocalAliasLocalPart) { + setState { + copy( + newLocalAliasState = RoomAliasViewState.AddAliasState.Editing(action.aliasLocalPart, Uninitialized) + ) + } + } + + private fun handleManualPublishAlias() = withState { state -> + val newAlias = (state.publishManuallyState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState + updateCanonicalAlias( + canonicalAlias = state.canonicalAlias, + alternativeAliases = state.alternativeAliases + newAlias, + closeForm = true + ) + } + + private fun handlePublishAlias(action: RoomAliasAction.PublishAlias) = withState { state -> + updateCanonicalAlias( + canonicalAlias = state.canonicalAlias, + alternativeAliases = state.alternativeAliases + action.alias, + closeForm = false + ) + } + + private fun handleUnpublishAlias(action: RoomAliasAction.UnpublishAlias) = withState { state -> + updateCanonicalAlias( + // We can also unpublish the canonical alias + canonicalAlias = state.canonicalAlias.takeIf { it != action.alias }, + alternativeAliases = state.alternativeAliases - action.alias, + closeForm = false + ) + } + + private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state -> + updateCanonicalAlias( + canonicalAlias = action.canonicalAlias, + // Ensure the previous canonical alias is moved to the alt aliases + alternativeAliases = state.allPublishedAliases, + closeForm = false + ) + } + + private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List, closeForm: Boolean) { + postLoading(true) + room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback { + override fun onSuccess(data: Unit) { + setState { + copy( + isLoading = false, + publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState + ) + } + } + + override fun onFailure(failure: Throwable) { + postLoading(false) + _viewEvents.post(RoomAliasViewEvents.Failure(failure)) + } + }) + } + + private fun handleAddLocalAlias() = withState { state -> + val previousState = (state.newLocalAliasState as? RoomAliasViewState.AddAliasState.Editing) ?: return@withState + + setState { + copy( + isLoading = true, + newLocalAliasState = previousState.copy(asyncRequest = Loading()) + ) + } + viewModelScope.launch { + runCatching { room.addAlias(previousState.value) } + .onFailure { + setState { + copy( + isLoading = false, + newLocalAliasState = previousState.copy(asyncRequest = Fail(it)) + ) + } + _viewEvents.post(RoomAliasViewEvents.Failure(it)) + } + .onSuccess { + setState { + copy( + isLoading = false, + newLocalAliasState = RoomAliasViewState.AddAliasState.Closed, + // Local echo + localAliases = Success((localAliases().orEmpty() + previousState.value).sorted()) + ) + } + fetchRoomAlias() + } + } + } + + private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) { + postLoading(true) + viewModelScope.launch { + runCatching { session.deleteRoomAlias(action.alias) } + .onFailure { + setState { + copy(isLoading = false) + } + _viewEvents.post(RoomAliasViewEvents.Failure(it)) + } + .onSuccess { + // Local echo + setState { + copy( + isLoading = false, + // Local echo + localAliases = Success(localAliases().orEmpty() - action.alias) + ) + } + fetchRoomAlias() + } + } + } + + private fun postLoading(isLoading: Boolean) { + setState { + copy(isLoading = isLoading) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt new file mode 100644 index 0000000000..f6341f4f64 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -0,0 +1,54 @@ +/* + * 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 com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.roomprofile.RoomProfileArgs +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +data class RoomAliasViewState( + val roomId: String, + val homeServerName: String = "", + val roomSummary: Async = Uninitialized, + val actionPermissions: ActionPermissions = ActionPermissions(), + val roomDirectoryVisibility: Async = Uninitialized, + val isLoading: Boolean = false, + val canonicalAlias: String? = null, + val alternativeAliases: List = emptyList(), + val publishManuallyState: AddAliasState = AddAliasState.Hidden, + val localAliases: Async> = Uninitialized, + val newLocalAliasState: AddAliasState = AddAliasState.Closed +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) + + val allPublishedAliases: List + get() = (alternativeAliases + listOfNotNull(canonicalAlias)).distinct() + + data class ActionPermissions( + val canChangeCanonicalAlias: Boolean = false + ) + + sealed class AddAliasState { + object Hidden : AddAliasState() + object Closed : AddAliasState() + data class Editing(val value: String, val asyncRequest: Async = Uninitialized) : AddAliasState() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt new file mode 100644 index 0000000000..86702d1507 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias.detail + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import kotlinx.android.parcel.Parcelize +import javax.inject.Inject + +@Parcelize +data class RoomAliasBottomSheetArgs( + val alias: String, + val isPublished: Boolean, + val isMainAlias: Boolean, + val isLocal: Boolean, + val canEditCanonicalAlias: Boolean +) : Parcelable + +/** + * Bottom sheet fragment that shows room alias information with list of contextual actions + */ +class RoomAliasBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomAliasBottomSheetController.Listener { + + private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel + @Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool + @Inject lateinit var roomAliasBottomSheetViewModelFactory: RoomAliasBottomSheetViewModel.Factory + @Inject lateinit var controller: RoomAliasBottomSheetController + + private val viewModel: RoomAliasBottomSheetViewModel by fragmentViewModel(RoomAliasBottomSheetViewModel::class) + + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView + + override val showExpanded = true + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java) + recyclerView.configureWith(controller, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun onDestroyView() { + recyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + super.invalidate() + } + + override fun didSelectMenuAction(quickAction: RoomAliasBottomSheetSharedAction) { + sharedActionViewModel.post(quickAction) + + dismiss() + } + + companion object { + fun newInstance(alias: String, + isPublished: Boolean, + isMainAlias: Boolean, + isLocal: Boolean, + canEditCanonicalAlias: Boolean): RoomAliasBottomSheet { + return RoomAliasBottomSheet().apply { + setArguments(RoomAliasBottomSheetArgs( + alias = alias, + isPublished = isPublished, + isMainAlias = isMainAlias, + isLocal = isLocal, + canEditCanonicalAlias = canEditCanonicalAlias + )) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt new file mode 100644 index 0000000000..157037c13d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.roomprofile.alias.detail + +import android.view.View +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem +import im.vector.app.core.epoxy.dividerItem +import im.vector.app.core.epoxy.profiles.profileActionItem +import javax.inject.Inject + +/** + * Epoxy controller for room alias actions + */ +class RoomAliasBottomSheetController @Inject constructor() : TypedEpoxyController() { + + var listener: Listener? = null + + override fun buildModels(state: RoomAliasBottomSheetState) { + profileActionItem { + id("alias") + title(state.alias) + subtitle(state.matrixToLink) + editable(false) + } + + // Notifications + dividerItem { + id("aliasSeparator") + } + + var idx = 0 + // Share + state.matrixToLink?.let { + RoomAliasBottomSheetSharedAction.ShareAlias(it).toBottomSheetItem(++idx) + } + + // Action on published alias + if (state.isPublished) { + // Published address + if (state.canEditCanonicalAlias) { + if (state.isMainAlias) { + RoomAliasBottomSheetSharedAction.UnsetMainAlias.toBottomSheetItem(++idx) + } else { + RoomAliasBottomSheetSharedAction.SetMainAlias(state.alias).toBottomSheetItem(++idx) + } + RoomAliasBottomSheetSharedAction.UnPublishAlias(state.alias).toBottomSheetItem(++idx) + } + } + + if (state.isLocal) { + // Local address + if (state.canEditCanonicalAlias && state.isPublished.not()) { + // Publish + RoomAliasBottomSheetSharedAction.PublishAlias(state.alias).toBottomSheetItem(++idx) + } + // Delete + RoomAliasBottomSheetSharedAction.DeleteAlias(state.alias).toBottomSheetItem(++idx) + } + } + + private fun RoomAliasBottomSheetSharedAction.toBottomSheetItem(index: Int) { + return bottomSheetActionItem { + id("action_$index") + iconRes(iconResId) + textRes(titleRes) + destructive(this@toBottomSheetItem.destructive) + listener(View.OnClickListener { listener?.didSelectMenuAction(this@toBottomSheetItem) }) + } + } + + interface Listener { + fun didSelectMenuAction(quickAction: RoomAliasBottomSheetSharedAction) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt new file mode 100644 index 0000000000..13909c401f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias.detail + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import im.vector.app.R +import im.vector.app.core.platform.VectorSharedAction + +sealed class RoomAliasBottomSheetSharedAction( + @StringRes val titleRes: Int, + @DrawableRes val iconResId: Int = 0, + val destructive: Boolean = false) + : VectorSharedAction { + + data class ShareAlias(val matrixTo: String) : RoomAliasBottomSheetSharedAction( + R.string.share, + R.drawable.ic_material_share + ) + + data class PublishAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.room_alias_action_publish + ) + + data class UnPublishAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.room_alias_action_unpublish + ) + + data class DeleteAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.delete, + R.drawable.ic_trash_24, + true + ) + + data class SetMainAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.room_settings_set_main_address + ) + + object UnsetMainAlias : RoomAliasBottomSheetSharedAction( + R.string.room_settings_unset_main_address + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt new file mode 100644 index 0000000000..5f71783515 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.features.roomprofile.alias.detail + +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +/** + * Activity shared view model to handle room alias quick actions + */ +class RoomAliasBottomSheetSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt new file mode 100644 index 0000000000..a61075cef6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias.detail + +import com.airbnb.mvrx.MvRxState + +data class RoomAliasBottomSheetState( + val alias: String, + val matrixToLink: String? = null, + val isPublished: Boolean, + val isMainAlias: Boolean, + val isLocal: Boolean, + val canEditCanonicalAlias: Boolean +) : MvRxState { + + constructor(args: RoomAliasBottomSheetArgs) : this( + alias = args.alias, + isPublished = args.isPublished, + isMainAlias = args.isMainAlias, + isLocal = args.isLocal, + canEditCanonicalAlias = args.canEditCanonicalAlias + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt new file mode 100644 index 0000000000..7f723cae53 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.roomprofile.alias.detail + +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.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import org.matrix.android.sdk.api.session.Session + +class RoomAliasBottomSheetViewModel @AssistedInject constructor( + @Assisted initialState: RoomAliasBottomSheetState, + session: Session +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel? { + val fragment: RoomAliasBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.roomAliasBottomSheetViewModelFactory.create(state) + } + } + + init { + setState { + copy( + matrixToLink = session.permalinkService().createPermalink(alias) + ) + } + } + + override fun handle(action: EmptyAction) { + // No op + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListAction.kt similarity index 82% rename from vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListAction.kt index ca7d567d90..8f6f5afba1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListAction.kt @@ -19,8 +19,8 @@ package im.vector.app.features.roomprofile.banned import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -sealed class RoomBannedListMemberAction : VectorViewModelAction { - data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction() - data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction() - data class Filter(val filter: String) : RoomBannedListMemberAction() +sealed class RoomBannedMemberListAction : VectorViewModelAction { + data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListAction() + data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListAction() + data class Filter(val filter: String) : RoomBannedMemberListAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt index 797e6c8aa3..349321c87a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt @@ -37,18 +37,18 @@ import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class RoomBannedMemberListFragment @Inject constructor( - val viewModelFactory: RoomBannedListMemberViewModel.Factory, + val viewModelFactory: RoomBannedMemberListViewModel.Factory, private val roomMemberListController: RoomBannedMemberListController, private val avatarRenderer: AvatarRenderer ) : VectorBaseFragment(), RoomBannedMemberListController.Callback { - private val viewModel: RoomBannedListMemberViewModel by fragmentViewModel() + private val viewModel: RoomBannedMemberListViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() override fun getLayoutResId() = R.layout.fragment_room_setting_generic override fun onUnbanClicked(roomMember: RoomMemberSummary) { - viewModel.handle(RoomBannedListMemberAction.QueryInfo(roomMember)) + viewModel.handle(RoomBannedMemberListAction.QueryInfo(roomMember)) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -60,7 +60,7 @@ class RoomBannedMemberListFragment @Inject constructor( viewModel.observeViewEvents { when (it) { - is RoomBannedViewEvents.ShowBannedInfo -> { + is RoomBannedMemberListViewEvents.ShowBannedInfo -> { val canBan = withState(viewModel) { state -> state.canUserBan } AlertDialog.Builder(requireActivity()) .setTitle(getString(R.string.member_banned_by, it.bannedByUserId)) @@ -69,13 +69,13 @@ class RoomBannedMemberListFragment @Inject constructor( .apply { if (canBan) { setNegativeButton(R.string.room_participants_action_unban) { _, _ -> - viewModel.handle(RoomBannedListMemberAction.UnBanUser(it.roomMemberSummary)) + viewModel.handle(RoomBannedMemberListAction.UnBanUser(it.roomMemberSummary)) } } } .show() } - is RoomBannedViewEvents.ToastError -> { + is RoomBannedMemberListViewEvents.ToastError -> { requireActivity().toast(it.info) } } @@ -96,7 +96,7 @@ class RoomBannedMemberListFragment @Inject constructor( } override fun onQueryTextChange(newText: String): Boolean { - viewModel.handle(RoomBannedListMemberAction.Filter(newText)) + viewModel.handle(RoomBannedMemberListAction.Filter(newText)) return true } }) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewEvents.kt similarity index 83% rename from vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedViewEvents.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewEvents.kt index 6b59debe96..4b1dc018ee 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomprofile.banned import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -sealed class RoomBannedViewEvents : VectorViewEvents { - data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedViewEvents() - data class ToastError(val info: String) : RoomBannedViewEvents() +sealed class RoomBannedMemberListViewEvents : VectorViewEvents { + data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListViewEvents() + data class ToastError(val info: String) : RoomBannedMemberListViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt similarity index 84% rename from vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 1cce2f96cb..0cecd22fa0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -42,14 +42,14 @@ import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap -class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, +class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, private val stringProvider: StringProvider, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { - fun create(initialState: RoomBannedMemberListViewState): RoomBannedListMemberViewModel + fun create(initialState: RoomBannedMemberListViewState): RoomBannedMemberListViewModel } private val room = session.getRoom(initialState.roomId)!! @@ -78,24 +78,24 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia }.disposeOnClear() } - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedListMemberViewModel? { + override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedMemberListViewModel? { val fragment: RoomBannedMemberListFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.viewModelFactory.create(state) } } - override fun handle(action: RoomBannedListMemberAction) { + override fun handle(action: RoomBannedMemberListAction) { when (action) { - is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary) - is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary) - is RoomBannedListMemberAction.Filter -> handleFilter(action) + is RoomBannedMemberListAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary) + is RoomBannedMemberListAction.UnBanUser -> unBanUser(action.roomMemberSummary) + is RoomBannedMemberListAction.Filter -> handleFilter(action) }.exhaustive } - private fun handleFilter(action: RoomBannedListMemberAction.Filter) { + private fun handleFilter(action: RoomBannedMemberListAction.Filter) { setState { copy( filter = action.filter @@ -114,7 +114,7 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia val reason = content.reason val bannedBy = bannedEvent?.senderId ?: return - _viewEvents.post(RoomBannedViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary)) + _viewEvents.post(RoomBannedMemberListViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary)) } private fun unBanUser(roomMemberSummary: RoomMemberSummary) { @@ -127,7 +127,7 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia room.unban(roomMemberSummary.userId, null, it) } } catch (failure: Throwable) { - _viewEvents.post(RoomBannedViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban))) + _viewEvents.post(RoomBannedMemberListViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban))) } finally { setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index f0a7b38478..867c605030 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -17,14 +17,17 @@ package im.vector.app.features.roomprofile.settings import im.vector.app.core.platform.VectorViewModelAction +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 sealed class RoomSettingsAction : VectorViewModelAction { data class SetAvatarAction(val avatarAction: RoomSettingsViewState.AvatarAction) : RoomSettingsAction() data class SetRoomName(val newName: String) : RoomSettingsAction() data class SetRoomTopic(val newTopic: String) : RoomSettingsAction() data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() - data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction() + data class SetRoomJoinRule(val roomJoinRule: RoomJoinRules?, val roomGuestAccess: GuestAccess?) : RoomSettingsAction() + object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 3c73e6ed46..bf3c1f87f8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -26,9 +26,8 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -46,7 +45,8 @@ class RoomSettingsController @Inject constructor( fun onNameChanged(name: String) fun onTopicChanged(topic: String) fun onHistoryVisibilityClicked() - fun onAliasChanged(alias: String) + fun onRoomAliasesClicked() + fun onJoinRuleClicked() } private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -60,20 +60,17 @@ class RoomSettingsController @Inject constructor( override fun buildModels(data: RoomSettingsViewState?) { val roomSummary = data?.roomSummary?.invoke() ?: return - val historyVisibility = data.historyVisibilityEvent?.let { formatRoomHistoryVisibilityEvent(it) } ?: "" - val newHistoryVisibility = data.newHistoryVisibility?.let { roomHistoryVisibilityFormatter.format(it) } - formEditableAvatarItem { id("avatar") enabled(data.actionPermissions.canChangeAvatar) when (val avatarAction = data.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(avatarRenderer) // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> + RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null) is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri) @@ -108,30 +105,48 @@ class RoomSettingsController @Inject constructor( } } - formEditTextItem { - id("alias") - enabled(data.actionPermissions.canChangeCanonicalAlias) - value(data.newCanonicalAlias ?: roomSummary.canonicalAlias) - hint(stringProvider.getString(R.string.room_settings_addresses_add_new_address)) - - onTextChange { text -> - callback?.onAliasChanged(text) - } - } + buildProfileAction( + id = "alias", + title = stringProvider.getString(R.string.room_settings_alias_title), + subtitle = stringProvider.getString(R.string.room_settings_alias_subtitle), + dividerColor = dividerColor, + divider = true, + editable = true, + action = { callback?.onRoomAliasesClicked() } + ) buildProfileAction( id = "historyReadability", title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title), - subtitle = newHistoryVisibility ?: historyVisibility, + subtitle = roomHistoryVisibilityFormatter.getSetting(data.newHistoryVisibility ?: data.currentHistoryVisibility), + dividerColor = dividerColor, + divider = true, + editable = data.actionPermissions.canChangeHistoryVisibility, + action = { if (data.actionPermissions.canChangeHistoryVisibility) callback?.onHistoryVisibilityClicked() } + ) + + buildProfileAction( + id = "joinRule", + title = stringProvider.getString(R.string.room_settings_room_access_title), + subtitle = data.getJoinRuleWording(), dividerColor = dividerColor, divider = false, - editable = data.actionPermissions.canChangeHistoryReadability, - action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() } + editable = data.actionPermissions.canChangeJoinRule, + action = { if (data.actionPermissions.canChangeJoinRule) callback?.onJoinRuleClicked() } ) } - private fun formatRoomHistoryVisibilityEvent(event: Event): String? { - val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null - return roomHistoryVisibilityFormatter.format(historyVisibility) + private fun RoomSettingsViewState.getJoinRuleWording(): String { + val joinRule = newRoomJoinRules.newJoinRules ?: currentRoomJoinRules + val guestAccess = newRoomJoinRules.newGuestAccess ?: currentGuestAccess + return stringProvider.getString(if (joinRule == RoomJoinRules.INVITE) { + R.string.room_settings_room_access_entry_only_invited + } else { + if (guestAccess == GuestAccess.CanJoin) { + R.string.room_settings_room_access_entry_anyone_with_link_including_guest + } else { + R.string.room_settings_room_access_entry_anyone_with_link_apart_guest + } + }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 6637b7d943..d8c8c41936 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -37,13 +37,15 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.toast import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.app.features.roomprofile.RoomProfileSharedAction +import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet +import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet +import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel 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.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.util.toMatrixItem import java.util.UUID import javax.inject.Inject @@ -51,7 +53,6 @@ import javax.inject.Inject class RoomSettingsFragment @Inject constructor( val viewModelFactory: RoomSettingsViewModel.Factory, private val controller: RoomSettingsController, - private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter, colorProvider: ColorProvider, private val avatarRenderer: AvatarRenderer ) : @@ -61,6 +62,10 @@ class RoomSettingsFragment @Inject constructor( GalleryOrCameraDialogHelper.Listener { private val viewModel: RoomSettingsViewModel by fragmentViewModel() + private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel + private lateinit var roomHistoryVisibilitySharedActionViewModel: RoomHistoryVisibilitySharedActionViewModel + private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel + private val roomProfileArgs: RoomProfileArgs by args() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) @@ -70,6 +75,9 @@ class RoomSettingsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) + setupRoomHistoryVisibilitySharedActionViewModel() + setupRoomJoinRuleSharedActionViewModel() controller.callback = this setupToolbar(roomSettingsToolbar) roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) @@ -88,6 +96,26 @@ class RoomSettingsFragment @Inject constructor( } } + private fun setupRoomJoinRuleSharedActionViewModel() { + roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) + roomJoinRuleSharedActionViewModel + .observe() + .subscribe { action -> + viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule, action.roomGuestAccess)) + } + .disposeOnDestroyView() + } + + private fun setupRoomHistoryVisibilitySharedActionViewModel() { + roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) + roomHistoryVisibilitySharedActionViewModel + .observe() + .subscribe { action -> + viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility)) + } + .disposeOnDestroyView() + } + private fun showSuccess() { activity?.toast(R.string.room_settings_save_success) } @@ -137,35 +165,20 @@ class RoomSettingsFragment @Inject constructor( } override fun onHistoryVisibilityClicked() = withState(viewModel) { state -> - val historyVisibilities = arrayOf( - RoomHistoryVisibility.SHARED, - RoomHistoryVisibility.INVITED, - RoomHistoryVisibility.JOINED, - RoomHistoryVisibility.WORLD_READABLE - ) - val currentHistoryVisibility = - state.newHistoryVisibility ?: state.historyVisibilityEvent?.getClearContent().toModel()?.historyVisibility - val currentHistoryVisibilityIndex = historyVisibilities.indexOf(currentHistoryVisibility) - - AlertDialog.Builder(requireContext()).apply { - setTitle(R.string.room_settings_room_read_history_rules_pref_title) - setSingleChoiceItems( - historyVisibilities - .map { roomHistoryVisibilityFormatter.format(it) } - .toTypedArray(), - currentHistoryVisibilityIndex) { dialog, which -> - if (which != currentHistoryVisibilityIndex) { - viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(historyVisibilities[which])) - } - dialog.cancel() - } - show() - } - return@withState + val currentHistoryVisibility = state.newHistoryVisibility ?: state.currentHistoryVisibility + RoomHistoryVisibilityBottomSheet.newInstance(currentHistoryVisibility) + .show(childFragmentManager, "RoomHistoryVisibilityBottomSheet") } - override fun onAliasChanged(alias: String) { - viewModel.handle(RoomSettingsAction.SetRoomCanonicalAlias(alias)) + override fun onRoomAliasesClicked() { + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings) + } + + override fun onJoinRuleClicked() = withState(viewModel) { state -> + val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules + val currentGuestAccess = state.newRoomJoinRules.newGuestAccess ?: state.currentGuestAccess + RoomJoinRuleBottomSheet.newInstance(currentJoinRule, currentGuestAccess) + .show(childFragmentManager, "RoomJoinRuleBottomSheet") } override fun onImageReady(uri: Uri?) { @@ -182,10 +195,10 @@ class RoomSettingsFragment @Inject constructor( override fun onAvatarDelete() { withState(viewModel) { when (it.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { viewModel.handle(RoomSettingsAction.SetAvatarAction(RoomSettingsViewState.AvatarAction.DeleteAvatar)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> { + RoomSettingsViewState.AvatarAction.DeleteAvatar -> { /* Should not happen */ Unit } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 05a75a585b..48ff38f92e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -33,6 +33,9 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.rx.mapOptional import org.matrix.android.sdk.rx.rx @@ -60,6 +63,9 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: init { observeRoomSummary() + observeRoomHistoryVisibility() + observeJoinRule() + observeGuestAccess() observeRoomAvatar() observeState() } @@ -68,14 +74,14 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: selectSubscribe( RoomSettingsViewState::avatarAction, RoomSettingsViewState::newName, - RoomSettingsViewState::newCanonicalAlias, RoomSettingsViewState::newTopic, RoomSettingsViewState::newHistoryVisibility, + RoomSettingsViewState::newRoomJoinRules, RoomSettingsViewState::roomSummary) { avatarAction, newName, - newCanonicalAlias, newTopic, newHistoryVisibility, + newJoinRule, asyncSummary -> val summary = asyncSummary() setState { @@ -83,8 +89,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: showSaveAction = avatarAction !is RoomSettingsViewState.AvatarAction.None || summary?.name != newName || summary?.topic != newTopic - || summary?.canonicalAlias != newCanonicalAlias?.takeIf { it.isNotEmpty() } - || newHistoryVisibility != null + || (newHistoryVisibility != null && newHistoryVisibility != currentHistoryVisibility) + || newJoinRule.hasChanged() ) } } @@ -96,11 +102,9 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: .execute { async -> val roomSummary = async.invoke() copy( - historyVisibilityEvent = room.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY), roomSummary = async, newName = roomSummary?.name, - newTopic = roomSummary?.topic, - newCanonicalAlias = roomSummary?.canonicalAlias + newTopic = roomSummary?.topic ) } @@ -113,16 +117,57 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), - canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_CANONICAL_ALIAS), - canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_HISTORY_VISIBILITY) + canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + EventType.STATE_ROOM_HISTORY_VISIBILITY), + canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + EventType.STATE_ROOM_JOIN_RULES) + && powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + EventType.STATE_ROOM_GUEST_ACCESS) ) setState { copy(actionPermissions = permissions) } } .disposeOnClear() } + private fun observeRoomHistoryVisibility() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + it.historyVisibility?.let { + setState { copy(currentHistoryVisibility = it) } + } + } + .disposeOnClear() + } + + private fun observeGuestAccess() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + it.joinRules?.let { + setState { copy(currentRoomJoinRules = it) } + } + } + .disposeOnClear() + } + + private fun observeJoinRule() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + it.guestAccess?.let { + setState { copy(currentGuestAccess = it) } + } + } + .disposeOnClear() + } + /** * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ @@ -143,12 +188,21 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) } is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) } is RoomSettingsAction.SetRoomHistoryVisibility -> setState { copy(newHistoryVisibility = action.visibility) } - is RoomSettingsAction.SetRoomCanonicalAlias -> setState { copy(newCanonicalAlias = action.newCanonicalAlias) } + is RoomSettingsAction.SetRoomJoinRule -> handleSetRoomJoinRule(action) is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() }.exhaustive } + private fun handleSetRoomJoinRule(action: RoomSettingsAction.SetRoomJoinRule) = withState { state -> + setState { + copy(newRoomJoinRules = RoomSettingsViewState.NewJoinRule( + action.roomJoinRule.takeIf { it != state.currentRoomJoinRules }, + action.roomGuestAccess.takeIf { it != state.currentGuestAccess } + )) + } + } + private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) { setState { deletePendingAvatar(this) @@ -191,15 +245,14 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: operationList.add(room.rx().updateTopic(state.newTopic ?: "")) } - if (state.newCanonicalAlias != null && summary?.canonicalAlias != state.newCanonicalAlias.takeIf { it.isNotEmpty() }) { - operationList.add(room.rx().addRoomAlias(state.newCanonicalAlias)) - operationList.add(room.rx().updateCanonicalAlias(state.newCanonicalAlias)) - } - if (state.newHistoryVisibility != null) { operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility)) } + if (state.newRoomJoinRules.hasChanged()) { + operationList.add(room.rx().updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess)) + } + Observable .fromIterable(operationList) .concatMapCompletable { it } @@ -210,7 +263,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: deletePendingAvatar(this) copy( avatarAction = RoomSettingsViewState.AvatarAction.None, - newHistoryVisibility = null + newHistoryVisibility = null, + newRoomJoinRules = RoomSettingsViewState.NewJoinRule() ) } _viewEvents.post(RoomSettingsViewEvents.Success) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 2cadc8f798..7403917d48 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -21,13 +21,17 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs -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.session.room.model.RoomSummary data class RoomSettingsViewState( val roomId: String, - val historyVisibilityEvent: Event? = null, + // Default value: https://matrix.org/docs/spec/client_server/r0.6.1#id88 + val currentHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.SHARED, + val currentRoomJoinRules: RoomJoinRules = RoomJoinRules.INVITE, + val currentGuestAccess: GuestAccess? = null, val roomSummary: Async = Uninitialized, val isLoading: Boolean = false, val currentRoomAvatarUrl: String? = null, @@ -35,7 +39,7 @@ data class RoomSettingsViewState( val newName: String? = null, val newTopic: String? = null, val newHistoryVisibility: RoomHistoryVisibility? = null, - val newCanonicalAlias: String? = null, + val newRoomJoinRules: NewJoinRule = NewJoinRule(), val showSaveAction: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions() ) : MvRxState { @@ -46,8 +50,8 @@ data class RoomSettingsViewState( val canChangeAvatar: Boolean = false, val canChangeName: Boolean = false, val canChangeTopic: Boolean = false, - val canChangeCanonicalAlias: Boolean = false, - val canChangeHistoryReadability: Boolean = false + val canChangeHistoryVisibility: Boolean = false, + val canChangeJoinRule: Boolean = false ) sealed class AvatarAction { @@ -56,4 +60,11 @@ data class RoomSettingsViewState( data class UpdateAvatar(val newAvatarUri: Uri, val newAvatarFileName: String) : AvatarAction() } + + data class NewJoinRule( + val newJoinRules: RoomJoinRules? = null, + val newGuestAccess: GuestAccess? = null + ) { + fun hasChanged() = newJoinRules != null || newGuestAccess != null + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt new file mode 100644 index 0000000000..3c989a7dbe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt @@ -0,0 +1,33 @@ +/* + * 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.roomprofile.settings.historyvisibility + +import androidx.annotation.DrawableRes +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericAction +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility + +class RoomHistoryVisibilityAction( + val roomHistoryVisibility: RoomHistoryVisibility, + title: String, + @DrawableRes iconResId: Int, + isSelected: Boolean +) : BottomSheetGenericAction( + title = title, + iconResId = iconResId, + isSelected = isSelected, + destructive = false +) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt new file mode 100644 index 0000000000..c12dc621a9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt @@ -0,0 +1,70 @@ +/* + * 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.roomprofile.settings.historyvisibility + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.ui.bottomsheet.BottomSheetGeneric +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import kotlinx.android.parcel.Parcelize +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import javax.inject.Inject + +@Parcelize +data class RoomHistoryVisibilityBottomSheetArgs( + val currentRoomHistoryVisibility: RoomHistoryVisibility +) : Parcelable + +class RoomHistoryVisibilityBottomSheet : BottomSheetGeneric() { + + private lateinit var roomHistoryVisibilitySharedActionViewModel: RoomHistoryVisibilitySharedActionViewModel + @Inject lateinit var controller: RoomHistoryVisibilityController + private val viewModel: RoomHistoryVisibilityViewModel by fragmentViewModel(RoomHistoryVisibilityViewModel::class) + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getController(): BottomSheetGenericController = controller + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) + } + + override fun didSelectAction(action: RoomHistoryVisibilityAction) { + roomHistoryVisibilitySharedActionViewModel.post(action) + dismiss() + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + super.invalidate() + } + + companion object { + fun newInstance(currentRoomHistoryVisibility: RoomHistoryVisibility): RoomHistoryVisibilityBottomSheet { + return RoomHistoryVisibilityBottomSheet().apply { + setArguments(RoomHistoryVisibilityBottomSheetArgs(currentRoomHistoryVisibility)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt new file mode 100644 index 0000000000..a4899711f7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -0,0 +1,51 @@ +/* + * 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.roomprofile.settings.historyvisibility + +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import javax.inject.Inject + +class RoomHistoryVisibilityController @Inject constructor( + private val historyVisibilityFormatter: RoomHistoryVisibilityFormatter, + private val stringProvider: StringProvider +) : BottomSheetGenericController() { + + override fun getTitle() = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_dialog_title) + + override fun getSubTitle() = stringProvider.getString(R.string.room_settings_room_read_history_dialog_subtitle) + + override fun getActions(state: RoomHistoryVisibilityState): List { + return listOf( + RoomHistoryVisibility.WORLD_READABLE, + RoomHistoryVisibility.SHARED, + RoomHistoryVisibility.INVITED, + RoomHistoryVisibility.JOINED + ) + .map { roomHistoryVisibility -> + RoomHistoryVisibilityAction( + roomHistoryVisibility = roomHistoryVisibility, + title = historyVisibilityFormatter.getSetting(roomHistoryVisibility), + iconResId = 0, + isSelected = roomHistoryVisibility == state.currentRoomHistoryVisibility + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt new file mode 100644 index 0000000000..31c1c2631c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.features.roomprofile.settings.historyvisibility + +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +class RoomHistoryVisibilitySharedActionViewModel @Inject constructor() + : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt new file mode 100644 index 0000000000..0b651d5664 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt @@ -0,0 +1,27 @@ +/* + * 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.roomprofile.settings.historyvisibility + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericState +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility + +data class RoomHistoryVisibilityState( + val currentRoomHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.SHARED +) : BottomSheetGenericState() { + + constructor(args: RoomHistoryVisibilityBottomSheetArgs) : this(currentRoomHistoryVisibility = args.currentRoomHistoryVisibility) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt new file mode 100644 index 0000000000..c2a8ae967f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt @@ -0,0 +1,22 @@ +/* + * 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.roomprofile.settings.historyvisibility + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericViewModel + +class RoomHistoryVisibilityViewModel(initialState: RoomHistoryVisibilityState) + : BottomSheetGenericViewModel(initialState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt new file mode 100644 index 0000000000..6f71669002 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt @@ -0,0 +1,35 @@ +/* + * 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.roomprofile.settings.joinrule + +import androidx.annotation.DrawableRes +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericAction +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules + +class RoomJoinRuleAction( + val roomJoinRule: RoomJoinRules, + val roomGuestAccess: GuestAccess?, + title: String, + @DrawableRes iconResId: Int, + isSelected: Boolean +) : BottomSheetGenericAction( + title = title, + iconResId = iconResId, + isSelected = isSelected, + destructive = false +) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt new file mode 100644 index 0000000000..66c6be6086 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt @@ -0,0 +1,72 @@ +/* + * 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.roomprofile.settings.joinrule + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.ui.bottomsheet.BottomSheetGeneric +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import kotlinx.android.parcel.Parcelize +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import javax.inject.Inject + +@Parcelize +data class RoomJoinRuleBottomSheetArgs( + val currentRoomJoinRule: RoomJoinRules, + val currentGuestAccess: GuestAccess? +) : Parcelable + +class RoomJoinRuleBottomSheet : BottomSheetGeneric() { + + private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel + @Inject lateinit var controller: RoomJoinRuleController + private val viewModel: RoomJoinRuleViewModel by fragmentViewModel(RoomJoinRuleViewModel::class) + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getController(): BottomSheetGenericController = controller + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) + } + + override fun didSelectAction(action: RoomJoinRuleAction) { + roomJoinRuleSharedActionViewModel.post(action) + dismiss() + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + super.invalidate() + } + + companion object { + fun newInstance(currentRoomJoinRule: RoomJoinRules, currentGuestAccess: GuestAccess?): RoomJoinRuleBottomSheet { + return RoomJoinRuleBottomSheet().apply { + setArguments(RoomJoinRuleBottomSheetArgs(currentRoomJoinRule, currentGuestAccess)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt new file mode 100644 index 0000000000..ab00396dbe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt @@ -0,0 +1,57 @@ +/* + * 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.roomprofile.settings.joinrule + +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import javax.inject.Inject + +class RoomJoinRuleController @Inject constructor( + private val stringProvider: StringProvider +) : BottomSheetGenericController() { + + override fun getTitle() = stringProvider.getString(R.string.room_settings_room_access_rules_pref_dialog_title) + + override fun getActions(state: RoomJoinRuleState): List { + return listOf( + RoomJoinRuleAction( + roomJoinRule = RoomJoinRules.INVITE, + roomGuestAccess = null, + title = stringProvider.getString(R.string.room_settings_room_access_entry_only_invited), + iconResId = 0, + isSelected = state.currentRoomJoinRule == RoomJoinRules.INVITE + ), + RoomJoinRuleAction( + roomJoinRule = RoomJoinRules.PUBLIC, + roomGuestAccess = GuestAccess.Forbidden, + title = stringProvider.getString(R.string.room_settings_room_access_entry_anyone_with_link_apart_guest), + iconResId = 0, + isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.Forbidden + ), + RoomJoinRuleAction( + roomJoinRule = RoomJoinRules.PUBLIC, + roomGuestAccess = GuestAccess.CanJoin, + title = stringProvider.getString(R.string.room_settings_room_access_entry_anyone_with_link_including_guest), + iconResId = 0, + isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.CanJoin + ) + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt new file mode 100644 index 0000000000..934b0dfc76 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.features.roomprofile.settings.joinrule + +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +class RoomJoinRuleSharedActionViewModel @Inject constructor() + : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt new file mode 100644 index 0000000000..ec16b02d60 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt @@ -0,0 +1,32 @@ +/* + * 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.roomprofile.settings.joinrule + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericState +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules + +data class RoomJoinRuleState( + val currentRoomJoinRule: RoomJoinRules = RoomJoinRules.INVITE, + val currentGuestAccess: GuestAccess? = null +) : BottomSheetGenericState() { + + constructor(args: RoomJoinRuleBottomSheetArgs) : this( + currentRoomJoinRule = args.currentRoomJoinRule, + currentGuestAccess = args.currentGuestAccess + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt new file mode 100644 index 0000000000..4305bfa72d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt @@ -0,0 +1,22 @@ +/* + * 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.roomprofile.settings.joinrule + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericViewModel + +class RoomJoinRuleViewModel(initialState: RoomJoinRuleState) + : BottomSheetGenericViewModel(initialState) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 76b1a9e0c3..763eed5474 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.session.room.uploads.GetUploadsResult import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx @@ -90,9 +89,7 @@ class RoomUploadsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val result = awaitCallback { - room.getUploads(20, token, it) - } + val result = room.getUploads(20, token) token = result.nextToken diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 5872c1fa1c..9d6ed0246c 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -165,6 +165,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { // Security const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE" const val SETTINGS_SECURITY_USE_PIN_CODE_FLAG = "SETTINGS_SECURITY_USE_PIN_CODE_FLAG" + const val SETTINGS_SECURITY_CHANGE_PIN_CODE_FLAG = "SETTINGS_SECURITY_CHANGE_PIN_CODE_FLAG" private const val SETTINGS_SECURITY_USE_BIOMETRICS_FLAG = "SETTINGS_SECURITY_USE_BIOMETRICS_FLAG" private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG" const val SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG = "SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index b1ccabfb76..5a7ceb4084 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -58,7 +58,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService @@ -214,7 +213,9 @@ class VectorSettingsGeneralFragment @Inject constructor( it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> // Disable it while updating the state, will be re-enabled by the account data listener. it.isEnabled = false - session.integrationManagerService().setIntegrationEnabled(newValue as Boolean, NoOpMatrixCallback()) + lifecycleScope.launch { + session.integrationManagerService().setIntegrationEnabled(newValue as Boolean) + } true } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index 37465258f6..1a04dab950 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -21,6 +21,7 @@ import androidx.preference.Preference import androidx.preference.SwitchPreference import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.preference.VectorPreference import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.pin.PinCodeStore @@ -41,6 +42,10 @@ class VectorSettingsPinFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!! } + private val changePinCodePref by lazy { + findPreference(VectorPreferences.SETTINGS_SECURITY_CHANGE_PIN_CODE_FLAG)!! + } + private val useCompleteNotificationPref by lazy { findPreference(VectorPreferences.SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG)!! } @@ -74,6 +79,17 @@ class VectorSettingsPinFragment @Inject constructor( } true } + + changePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + if (hasPinCode) { + navigator.openPinCode( + requireContext(), + pinActivityResultLauncher, + PinMode.MODIFY + ) + } + true + } } } diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt index df822807ee..89d6e970cc 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt @@ -28,8 +28,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.terms.GetTermsResponse -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber class ReviewTermsViewModel @AssistedInject constructor( @@ -94,15 +92,12 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { - session.agreeToTerms( - termsArgs.type, - termsArgs.baseURL, - agreedUrls, - termsArgs.token, - it - ) - } + session.agreeToTerms( + termsArgs.type, + termsArgs.baseURL, + agreedUrls, + termsArgs.token + ) _viewEvents.post(ReviewTermsViewEvents.Success) } catch (failure: Throwable) { Timber.e(failure, "Failed to agree to terms") @@ -122,9 +117,7 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val data = awaitCallback { - session.getTerms(termsArgs.type, termsArgs.baseURL, it) - } + val data = session.getTerms(termsArgs.type, termsArgs.baseURL) val terms = data.serverResponse.getLocalizedTerms(action.preferredLanguageCode).map { Term(it.localizedUrl ?: "", it.localizedName ?: "", diff --git a/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt b/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt index 3aba6a4dad..847caeab4c 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt @@ -24,23 +24,19 @@ import im.vector.app.R * Note that style for light theme is default and is declared in the Android Manifest */ sealed class ActivityOtherThemes(@StyleRes val dark: Int, - @StyleRes val black: Int, - @StyleRes val status: Int) { + @StyleRes val black: Int) { object Default : ActivityOtherThemes( R.style.AppTheme_Dark, - R.style.AppTheme_Black, - R.style.AppTheme_Status + R.style.AppTheme_Black ) object AttachmentsPreview : ActivityOtherThemes( - R.style.AppTheme_AttachmentsPreview, R.style.AppTheme_AttachmentsPreview, R.style.AppTheme_AttachmentsPreview ) object VectorAttachmentsPreview : ActivityOtherThemes( - R.style.AppTheme_Transparent, R.style.AppTheme_Transparent, R.style.AppTheme_Transparent ) diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt index 18faa07954..bba6b9c253 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt @@ -24,6 +24,7 @@ import android.view.Menu import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.core.content.ContextCompat +import androidx.core.content.edit import androidx.core.graphics.drawable.DrawableCompat import im.vector.app.R import im.vector.app.core.di.DefaultSharedPreferences @@ -41,7 +42,6 @@ object ThemeUtils { private const val THEME_DARK_VALUE = "dark" private const val THEME_LIGHT_VALUE = "light" private const val THEME_BLACK_VALUE = "black" - private const val THEME_STATUS_VALUE = "status" private var currentTheme = AtomicReference(null) @@ -58,9 +58,8 @@ object ThemeUtils { */ fun isLightTheme(context: Context): Boolean { return when (getApplicationTheme(context)) { - THEME_LIGHT_VALUE, - THEME_STATUS_VALUE -> true - else -> false + THEME_LIGHT_VALUE -> true + else -> false } } @@ -73,8 +72,13 @@ object ThemeUtils { fun getApplicationTheme(context: Context): String { val currentTheme = this.currentTheme.get() return if (currentTheme == null) { - val themeFromPref = DefaultSharedPreferences.getInstance(context) - .getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) ?: THEME_LIGHT_VALUE + val prefs = DefaultSharedPreferences.getInstance(context) + var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) ?: THEME_LIGHT_VALUE + if (themeFromPref == "status") { + // Migrate to light theme, which is the closest theme + themeFromPref = THEME_LIGHT_VALUE + prefs.edit { putString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) } + } this.currentTheme.set(themeFromPref) themeFromPref } else { @@ -92,7 +96,6 @@ object ThemeUtils { when (aTheme) { THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark) THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black) - THEME_STATUS_VALUE -> context.setTheme(R.style.AppTheme_Status) else -> context.setTheme(R.style.AppTheme_Light) } @@ -109,7 +112,6 @@ object ThemeUtils { when (getApplicationTheme(activity)) { THEME_DARK_VALUE -> activity.setTheme(otherThemes.dark) THEME_BLACK_VALUE -> activity.setTheme(otherThemes.black) - THEME_STATUS_VALUE -> activity.setTheme(otherThemes.status) } mColorByAttr.clear() diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index cb40e5672b..eb588ec9ae 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.net.URL @@ -106,14 +105,11 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in if (state.permissionData()?.isWebviewWidget.orFalse()) { WidgetPermissionsHelper(integrationManagerService, widgetService).changePermission(state.roomId, widgetId, false) } else { - awaitCallback { - session.integrationManagerService().setNativeWidgetDomainAllowed( - state.permissionData.invoke()?.widget?.type?.preferred ?: "", - state.permissionData.invoke()?.widgetDomain ?: "", - false, - it - ) - } + session.integrationManagerService().setNativeWidgetDomainAllowed( + state.permissionData.invoke()?.widget?.type?.preferred ?: "", + state.permissionData.invoke()?.widgetDomain ?: "", + false + ) } } catch (failure: Throwable) { Timber.v("Failure revoking widget: ${state.widgetId}") @@ -131,14 +127,11 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in if (state.permissionData()?.isWebviewWidget.orFalse()) { WidgetPermissionsHelper(integrationManagerService, widgetService).changePermission(state.roomId, widgetId, true) } else { - awaitCallback { - session.integrationManagerService().setNativeWidgetDomainAllowed( - state.permissionData.invoke()?.widget?.type?.preferred ?: "", - state.permissionData.invoke()?.widgetDomain ?: "", - true, - it - ) - } + session.integrationManagerService().setNativeWidgetDomainAllowed( + state.permissionData.invoke()?.widget?.type?.preferred ?: "", + state.permissionData.invoke()?.widgetDomain ?: "", + true + ) } } catch (failure: Throwable) { Timber.v("Failure allowing widget: ${state.widgetId}") diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt index 871e73592d..5664609a99 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt @@ -19,7 +19,6 @@ package im.vector.app.features.widgets.permissions import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.widgets.WidgetService -import org.matrix.android.sdk.internal.util.awaitCallback class WidgetPermissionsHelper(private val integrationManagerService: IntegrationManagerService, private val widgetService: WidgetService) { @@ -30,8 +29,6 @@ class WidgetPermissionsHelper(private val integrationManagerService: Integration widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.SENSITIVE) ).firstOrNull() val eventId = widget?.event?.eventId ?: return - awaitCallback { - integrationManagerService.setWidgetAllowed(eventId, allow, it) - } + integrationManagerService.setWidgetAllowed(eventId, allow) } } diff --git a/vector/src/main/res/drawable/ic_trash_24.xml b/vector/src/main/res/drawable/ic_trash_24.xml index 266855d50c..27ad2e29d7 100644 --- a/vector/src/main/res/drawable/ic_trash_24.xml +++ b/vector/src/main/res/drawable/ic_trash_24.xml @@ -1,41 +1,47 @@ - - - - - + + + + + diff --git a/vector/src/main/res/layout/fragment_progress.xml b/vector/src/main/res/layout/fragment_progress.xml new file mode 100644 index 0000000000..a7a2076209 --- /dev/null +++ b/vector/src/main/res/layout/fragment_progress.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/vector/src/main/res/layout/item_bottom_sheet_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml index 8b5716cd8e..7456f50670 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_action.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml @@ -13,6 +13,7 @@ android:paddingEnd="@dimen/layout_horizontal_margin" android:paddingBottom="8dp"> + + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_room_actions_notifications_all" /> - + app:layout_constraintStart_toEndOf="@id/actionIcon" + app:layout_constraintTop_toTopOf="parent"> + + + - + tools:ignore="MissingPrefix" + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/item_bottom_sheet_title.xml b/vector/src/main/res/layout/item_bottom_sheet_title.xml new file mode 100644 index 0000000000..5113c43f39 --- /dev/null +++ b/vector/src/main/res/layout/item_bottom_sheet_title.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/vector/src/main/res/layout/item_form_text_input.xml b/vector/src/main/res/layout/item_form_text_input.xml index 594bfc1788..f7ce8e1c9f 100644 --- a/vector/src/main/res/layout/item_form_text_input.xml +++ b/vector/src/main/res/layout/item_form_text_input.xml @@ -20,10 +20,12 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> + diff --git a/vector/src/main/res/layout/item_room_alias_text_input.xml b/vector/src/main/res/layout/item_room_alias_text_input.xml index 9216fc6b7e..fd7a99f0f0 100644 --- a/vector/src/main/res/layout/item_room_alias_text_input.xml +++ b/vector/src/main/res/layout/item_room_alias_text_input.xml @@ -30,11 +30,13 @@ app:layout_constraintStart_toEndOf="@+id/itemRoomAliasHash" app:layout_constraintTop_toTopOf="parent"> + diff --git a/vector/src/main/res/layout/item_settings_three_pid.xml b/vector/src/main/res/layout/item_settings_three_pid.xml index a175788d86..0040840ce9 100644 --- a/vector/src/main/res/layout/item_settings_three_pid.xml +++ b/vector/src/main/res/layout/item_settings_three_pid.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?riotx_background" android:minHeight="64dp" android:paddingStart="@dimen/layout_horizontal_margin" android:paddingEnd="@dimen/layout_horizontal_margin"> @@ -12,19 +13,20 @@ android:id="@+id/item_settings_three_pid_icon" android:layout_width="16dp" android:layout_height="16dp" + android:layout_marginEnd="8dp" android:scaleType="center" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/item_settings_three_pid_title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:src="@drawable/ic_phone" app:tint="?riotx_text_secondary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_phone" /> - - - - - - - - - - -