From 55e8f519dfb638081f19ac7c98a641b74c5d9cfa Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 2 Apr 2021 11:02:32 +0100 Subject: [PATCH 001/230] Convert RoomService to suspend functions Signed-off-by: Dominic Fischer --- .../org/matrix/android/sdk/rx/RxSession.kt | 8 +-- .../android/sdk/common/CryptoTestHelper.kt | 22 +++---- .../crypto/gossiping/KeyShareTests.kt | 14 ++--- .../sdk/api/session/room/RoomService.kt | 32 ++++------ .../session/room/DefaultRoomService.kt | 61 +++++-------------- .../features/createdirect/DirectRoomHelper.kt | 5 +- .../VerificationBottomSheetViewModel.kt | 52 ++++++++-------- .../home/room/detail/RoomDetailViewModel.kt | 29 +++++---- .../home/room/list/RoomListViewModel.kt | 10 ++- .../roomdirectory/RoomDirectoryViewModel.kt | 12 ++-- .../createroom/CreateRoomViewModel.kt | 32 +++++----- .../roompreview/RoomPreviewViewModel.kt | 17 ++---- 12 files changed, 129 insertions(+), 165 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index 0d5b5ed821..cac0a32232 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -124,8 +124,8 @@ class RxSession(private val session: Session) { .startWithCallable { session.getPendingThreePids() } } - fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder { - session.createRoom(roomParams, it) + fun createRoom(roomParams: CreateRoomParams): Single = rxSingle { + session.createRoom(roomParams) } fun searchUsersDirectory(search: String, @@ -136,8 +136,8 @@ class RxSession(private val session: Session) { fun joinRoom(roomIdOrAlias: String, reason: String? = null, - viaServers: List = emptyList()): Single = singleBuilder { - session.joinRoom(roomIdOrAlias, reason, viaServers, it) + viaServers: List = emptyList()): Single = rxSingle { + session.joinRoom(roomIdOrAlias, reason, viaServers) } fun getRoomIdByAlias(roomAlias: String, diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index 5815b23c06..da176491c6 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -66,8 +66,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) - val roomId = mTestHelper.doSync { - aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) + val roomId = mTestHelper.runBlockingTest { + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) } if (encryptedRoom) { @@ -135,7 +135,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { bobRoomSummariesLive.observeForever(roomJoinedObserver) } - mTestHelper.doSync { bobSession.joinRoom(aliceRoomId, callback = it) } + mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) } mTestHelper.await(lock) @@ -176,8 +176,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { room.invite(samSession.myUserId, null) } - mTestHelper.doSync { - samSession.joinRoom(room.roomId, null, emptyList(), it) + mTestHelper.runBlockingTest { + samSession.joinRoom(room.roomId, null, emptyList()) } return samSession @@ -256,8 +256,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } fun createDM(alice: Session, bob: Session): String { - val roomId = mTestHelper.doSync { - alice.createDirectRoom(bob.myUserId, it) + val roomId = mTestHelper.runBlockingTest { + alice.createDirectRoom(bob.myUserId) } mTestHelper.waitWithLatch { latch -> @@ -300,7 +300,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { bobRoomSummariesLive.observeForever(newRoomObserver) } - mTestHelper.doSync { bob.joinRoom(roomId, callback = it) } + mTestHelper.runBlockingTest { bob.joinRoom(roomId) } } return roomId @@ -398,8 +398,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) aliceSession.cryptoService().setWarnOnUnknownDevices(false) - val roomId = mTestHelper.doSync { - aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) + val roomId = mTestHelper.runBlockingTest { + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }) } val room = aliceSession.getRoom(roomId)!! @@ -412,7 +412,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val session = mTestHelper.createAccount("User_$index", defaultSessionParams) mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } println("TEST -> " + session.myUserId + " invited") - mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } + mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) } println("TEST -> " + session.myUserId + " joined") sessions.add(session) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index e6b364f3fb..40659cef11 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -71,13 +71,12 @@ class KeyShareTests : InstrumentedTest { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) // Create an encrypted room and add a message - val roomId = mTestHelper.doSync { + val roomId = mTestHelper.runBlockingTest { aliceSession.createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() - }, - it + } ) } val room = aliceSession.getRoom(roomId) @@ -332,13 +331,12 @@ class KeyShareTests : InstrumentedTest { } // Create an encrypted room and send a couple of messages - val roomId = mTestHelper.doSync { + val roomId = mTestHelper.runBlockingTest { aliceSession.createRoom( CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() - }, - it + } ) } val roomAlicePov = aliceSession.getRoom(roomId) @@ -371,8 +369,8 @@ class KeyShareTests : InstrumentedTest { roomAlicePov.invite(bobSession.myUserId, null) } - mTestHelper.doSync { - bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it) + mTestHelper.runBlockingTest { + bobSession.joinRoom(roomAlicePov.roomId, null, emptyList()) } // we want to discard alice outbound session 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 5f02b77a1e..bc5cb3c8f4 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 @@ -17,14 +17,12 @@ 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.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription @@ -36,22 +34,19 @@ interface RoomService { /** * Create a room asynchronously */ - fun createRoom(createRoomParams: CreateRoomParams, - callback: MatrixCallback): Cancelable + suspend fun createRoom(createRoomParams: CreateRoomParams): String /** * Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters */ - fun createDirectRoom(otherUserId: String, - callback: MatrixCallback): Cancelable { + suspend fun createDirectRoom(otherUserId: String): String { return createRoom( CreateRoomParams() .apply { invitedUserIds.add(otherUserId) setDirectMessage() enableEncryptionIfInvitedUsersSupportIt = true - }, - callback + } ) } @@ -61,10 +56,9 @@ interface RoomService { * @param reason optional reason for joining the room * @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room. */ - fun joinRoom(roomIdOrAlias: String, - reason: String? = null, - viaServers: List = emptyList(), - callback: MatrixCallback): Cancelable + suspend fun joinRoom(roomIdOrAlias: String, + reason: String? = null, + viaServers: List = emptyList()) /** * Get a room from a roomId @@ -110,20 +104,18 @@ interface RoomService { * Inform the Matrix SDK that a room is displayed. * The SDK will update the breadcrumbs in the user account data */ - fun onRoomDisplayed(roomId: String): Cancelable + suspend fun onRoomDisplayed(roomId: String) /** * Mark all rooms as read */ - fun markAllAsRead(roomIds: List, - callback: MatrixCallback): Cancelable + suspend fun markAllAsRead(roomIds: List) /** * Resolve a room alias to a room ID. */ - fun getRoomIdByAlias(roomAlias: String, - searchOnServer: Boolean, - callback: MatrixCallback>): Cancelable + suspend fun getRoomIdByAlias(roomAlias: String, + searchOnServer: Boolean): Optional /** * Delete a room alias @@ -170,12 +162,12 @@ interface RoomService { /** * Get some state events about a room */ - fun getRoomState(roomId: String, callback: MatrixCallback>) + suspend fun getRoomState(roomId: String): List /** * Use this if you want to get information from a room that you are not yet in (or invited) * It might be possible to get some information on this room if it is public or if guest access is allowed * This call will try to gather some information on this room, but it could fail and get nothing more */ - fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) + suspend fun peekRoom(roomIdOrAlias: String): PeekResult } 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 383dd876d3..f510b3c997 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 @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.RoomService @@ -29,7 +28,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -47,8 +45,6 @@ import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.fetchCopied import javax.inject.Inject @@ -64,16 +60,11 @@ internal class DefaultRoomService @Inject constructor( private val peekRoomTask: PeekRoomTask, private val roomGetter: RoomGetter, private val roomSummaryDataSource: RoomSummaryDataSource, - private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, - private val taskExecutor: TaskExecutor + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource ) : RoomService { - override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable { - return createRoomTask - .configureWith(createRoomParams) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun createRoom(createRoomParams: CreateRoomParams): String { + return createRoomTask.execute(createRoomParams) } override fun getRoom(roomId: String): Room? { @@ -104,34 +95,20 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getBreadcrumbsLive(queryParams) } - override fun onRoomDisplayed(roomId: String): Cancelable { - return updateBreadcrumbsTask - .configureWith(UpdateBreadcrumbsTask.Params(roomId)) - .executeBy(taskExecutor) + override suspend fun onRoomDisplayed(roomId: String) { + updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId)) } - override fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { - return joinRoomTask - .configureWith(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List) { + joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) } - override fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable { - return markAllRoomsReadTask - .configureWith(MarkAllRoomsReadTask.Params(roomIds)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun markAllAsRead(roomIds: List) { + markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds)) } - override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback>): Cancelable { - return roomIdByAliasTask - .configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Optional { + return roomIdByAliasTask.execute(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) } override suspend fun deleteRoomAlias(roomAlias: String) { @@ -162,19 +139,11 @@ internal class DefaultRoomService @Inject constructor( } } - override fun getRoomState(roomId: String, callback: MatrixCallback>) { - resolveRoomStateTask - .configureWith(ResolveRoomStateTask.Params(roomId)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun getRoomState(roomId: String): List { + return resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId)) } - override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) { - peekRoomTask - .configureWith(PeekRoomTask.Params(roomIdOrAlias)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun peekRoom(roomIdOrAlias: String): PeekResult { + return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias)) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt index 171970ec1e..bfa56cfb9e 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject class DirectRoomHelper @Inject constructor( @@ -45,9 +44,7 @@ class DirectRoomHelper @Inject constructor( setDirectMessage() enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - roomId = awaitCallback { - session.createRoom(roomParams, it) - } + roomId = session.createRoom(roomParams) } return roomId } 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 0e230c6727..a67cb96d37 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 @@ -249,32 +249,34 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( pendingRequest = Loading() ) } - session.createDirectRoom(otherUserId, object : MatrixCallback { - override fun onSuccess(data: String) { - setState { - copy( - roomId = data, - pendingRequest = Success( - session - .cryptoService() - .verificationService() - .requestKeyVerificationInDMs( - supportedVerificationMethodsProvider.provide(), - otherUserId, - data, - pendingLocalId - ) + viewModelScope.launch { + val result = runCatching { session.createDirectRoom(otherUserId) } + result.fold( + { data -> + setState { + copy( + roomId = data, + pendingRequest = Success( + session + .cryptoService() + .verificationService() + .requestKeyVerificationInDMs( + supportedVerificationMethodsProvider.provide(), + otherUserId, + data, + pendingLocalId + ) + ) ) - ) - } - } - - override fun onFailure(failure: Throwable) { - setState { - copy(pendingRequest = Fail(failure)) - } - } - }) + } + }, + { failure -> + setState { + copy(pendingRequest = Fail(failure)) + } + } + ) + } } else { setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 6152562850..ce53c72376 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -184,7 +184,12 @@ class RoomDetailViewModel @AssistedInject constructor( } } // Inform the SDK that the room is displayed - session.onRoomDisplayed(initialState.roomId) + viewModelScope.launch { + try { + session.onRoomDisplayed(initialState.roomId) + } catch (_: Exception) { + } + } callManager.addPstnSupportListener(this) callManager.checkForPSTNSupportIfNeeded() chatEffectManager.delegate = this @@ -902,19 +907,19 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { - session.joinRoom(command.roomAlias, command.reason, emptyList(), object : MatrixCallback { - override fun onSuccess(data: Unit) { - session.getRoomSummary(command.roomAlias) - ?.roomId - ?.let { - _viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSuccess(it)) - } - } - - override fun onFailure(failure: Throwable) { + viewModelScope.launch { + try { + session.joinRoom(command.roomAlias, command.reason, emptyList()) + } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) + return@launch } - }) + session.getRoomSummary(command.roomAlias) + ?.roomId + ?.let { + _viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSuccess(it)) + } + } } private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 3a5e797f98..f4cfbe94d0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -26,7 +26,6 @@ import im.vector.app.core.utils.DataSource import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership @@ -169,7 +168,14 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ?.filter { it.membership == Membership.JOIN } ?.map { it.roomId } ?.toList() - ?.let { session.markAllAsRead(it, NoOpMatrixCallback()) } + ?.let { + viewModelScope.launch { + try { + session.markAllAsRead(it) + } catch (_: Exception) { + } + } + } } private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 4ef38758c7..5012243e96 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -31,7 +31,6 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session @@ -232,17 +231,16 @@ class RoomDirectoryViewModel @AssistedInject constructor( val viaServers = state.roomDirectoryData.homeServer ?.let { listOf(it) } .orEmpty() - session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { + viewModelScope.launch { + try { + session.joinRoom(action.roomId, viaServers = viaServers) // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { // Notify the user _viewEvents.post(RoomDirectoryViewEvents.Failure(failure)) } - }) + } } override fun onCleared() { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 3c027c4845..af63f23a8c 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -34,7 +34,6 @@ import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session @@ -216,19 +215,22 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr } } - session.createRoom(createRoomParams, object : MatrixCallback { - override fun onSuccess(data: String) { - setState { - copy(asyncCreateRoomRequest = Success(data)) - } - } - - override fun onFailure(failure: Throwable) { - setState { - copy(asyncCreateRoomRequest = Fail(failure)) - } - _viewEvents.post(CreateRoomViewEvents.Failure(failure)) - } - }) + // TODO: Should this be non-cancellable? + viewModelScope.launch { + val result = runCatching { session.createRoom(createRoomParams) } + result.fold( + { roomId -> + setState { + copy(asyncCreateRoomRequest = Success(roomId)) + } + }, + { failure -> + setState { + copy(asyncCreateRoomRequest = Fail(failure)) + } + _viewEvents.post(CreateRoomViewEvents.Failure(failure)) + } + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt index c99213d890..c4d479374b 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/roompreview/RoomPreviewViewModel.kt @@ -31,7 +31,6 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.roomdirectory.JoinState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -39,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber @@ -77,9 +75,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini } viewModelScope.launch(Dispatchers.IO) { val peekResult = tryOrNull { - awaitCallback { - session.peekRoom(initialState.roomAlias ?: initialState.roomId, it) - } + session.peekRoom(initialState.roomAlias ?: initialState.roomId) } when (peekResult) { @@ -177,15 +173,14 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini Timber.w("Try to join an already joining room. Should not happen") return@withState } - session.joinRoom(state.roomId, viaServers = state.homeServers, callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { + viewModelScope.launch { + try { + session.joinRoom(state.roomId, viaServers = state.homeServers) // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { setState { copy(lastError = failure) } } - }) + } } } From 505b01ad97a141288738f17ca779037bcff0b4d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 07:19:20 +0000 Subject: [PATCH 002/230] Bump daggerVersion from 2.33 to 2.34 Bumps `daggerVersion` from 2.33 to 2.34. Updates `dagger` from 2.33 to 2.34 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.33...dagger-2.34) Updates `dagger-compiler` from 2.33 to 2.34 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.33...dagger-2.34) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 34460c6ab5..669444d563 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -112,7 +112,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.33' + def daggerVersion = '2.34' def work_version = '2.5.0' def retrofit_version = '2.9.0' diff --git a/vector/build.gradle b/vector/build.gradle index d5a105d893..38d3a4abbc 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -297,7 +297,7 @@ dependencies { def big_image_viewer_version = '1.7.1' def glide_version = '4.12.0' def moshi_version = '1.12.0' - def daggerVersion = '2.33' + def daggerVersion = '2.34' def autofill_version = "1.1.0" def work_version = '2.5.0' def arch_version = '2.1.0' From 3ca00969c53cc84f0497d094968fd45bf6a10f70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 07:22:02 +0000 Subject: [PATCH 003/230] Bump media from 1.2.1 to 1.3.0 Bumps media from 1.2.1 to 1.3.0. Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index d5a105d893..511e2f7561 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -326,7 +326,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation "androidx.sharetarget:sharetarget:1.1.0" implementation 'androidx.core:core-ktx:1.3.2' - implementation "androidx.media:media:1.2.1" + implementation "androidx.media:media:1.3.0" implementation "org.threeten:threetenbp:1.4.0:no-tzdb" implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" From 68efa63de4cde655b6fd0b82834129c567e655ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Apr 2021 07:22:33 +0000 Subject: [PATCH 004/230] Bump recyclerview from 1.2.0-rc01 to 1.2.0 Bumps recyclerview from 1.2.0-rc01 to 1.2.0. Signed-off-by: dependabot[bot] --- attachment-viewer/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 5a8cce92e8..8db57a59af 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -69,7 +69,7 @@ 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.recyclerview:recyclerview:1.2.0-rc01" + implementation "androidx.recyclerview:recyclerview:1.2.0" implementation 'com.google.android.material:material:1.3.0' } \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index d5a105d893..2da7349a5e 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -320,7 +320,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" - implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" + implementation "androidx.recyclerview:recyclerview:1.2.0" implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation 'androidx.constraintlayout:constraintlayout:2.0.4' From b5b651244b8af586eb7a57aa039781fdb2c0a857 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Apr 2021 07:25:53 +0000 Subject: [PATCH 005/230] Bump firebase-messaging from 21.0.1 to 21.1.0 Bumps firebase-messaging from 21.0.1 to 21.1.0. Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index d5a105d893..393db17033 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -423,7 +423,7 @@ dependencies { kapt "com.google.dagger:dagger-compiler:$daggerVersion" // gplay flavor only - gplayImplementation('com.google.firebase:firebase-messaging:21.0.1') { + gplayImplementation('com.google.firebase:firebase-messaging:21.1.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' From bec6cfc46dd4f790391c66434ddd066815b10153 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Apr 2021 12:42:59 +0200 Subject: [PATCH 006/230] Version++ --- CHANGES.md | 27 +++++++++++++++++++++++++++ vector/build.gradle | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 40228f7db8..a73551cc33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,30 @@ +Changes in Element 1.1.5 (2021-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Test: + - + +Other changes: + - + Changes in Element 1.1.4 (2021-04-09) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index d5a105d893..d561382e2d 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -14,7 +14,7 @@ kapt { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 1 -ext.versionPatch = 4 +ext.versionPatch = 5 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 37226a1641d9d437def9a1304f3d58e00d031a80 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Apr 2021 16:58:41 +0200 Subject: [PATCH 007/230] Small cleanup --- matrix-sdk-android-rx/src/main/AndroidManifest.xml | 3 +-- .../android/sdk/internal/crypto/model/CryptoDeviceInfo.kt | 4 ++-- .../android/sdk/internal/crypto/model/MXDeviceInfo.kt | 4 ++-- .../java/im/vector/app/core/resources/AppNameProvider.kt | 6 +++--- .../im/vector/app/features/call/dialpad/DialPadFragment.kt | 2 +- vector/src/main/res/layout/dialog_export_e2e_keys.xml | 1 - 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/AndroidManifest.xml b/matrix-sdk-android-rx/src/main/AndroidManifest.xml index f1bb42638f..5f399e9f84 100644 --- a/matrix-sdk-android-rx/src/main/AndroidManifest.xml +++ b/matrix-sdk-android-rx/src/main/AndroidManifest.xml @@ -1,2 +1 @@ - + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt index 7eebbd9b2c..4004294d97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt @@ -44,7 +44,7 @@ data class CryptoDeviceInfo( */ fun fingerprint(): String? { return keys - ?.takeIf { !deviceId.isBlank() } + ?.takeIf { deviceId.isNotBlank() } ?.get("ed25519:$deviceId") } @@ -53,7 +53,7 @@ data class CryptoDeviceInfo( */ fun identityKey(): String? { return keys - ?.takeIf { !deviceId.isBlank() } + ?.takeIf { deviceId.isNotBlank() } ?.get("curve25519:$deviceId") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt index 3c651c27a0..00b8bde5d9 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt @@ -103,7 +103,7 @@ data class MXDeviceInfo( */ fun fingerprint(): String? { return keys - ?.takeIf { !deviceId.isBlank() } + ?.takeIf { deviceId.isNotBlank() } ?.get("ed25519:$deviceId") } @@ -112,7 +112,7 @@ data class MXDeviceInfo( */ fun identityKey(): String? { return keys - ?.takeIf { !deviceId.isBlank() } + ?.takeIf { deviceId.isNotBlank() } ?.get("curve25519:$deviceId") } diff --git a/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt b/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt index 9874c1744f..90558e35b7 100644 --- a/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt @@ -23,7 +23,7 @@ import javax.inject.Inject class AppNameProvider @Inject constructor(private val context: Context) { fun getAppName(): String { - try { + return try { val appPackageName = context.applicationContext.packageName val pm = context.packageManager val appInfo = pm.getApplicationInfo(appPackageName, 0) @@ -33,10 +33,10 @@ class AppNameProvider @Inject constructor(private val context: Context) { if (!appName.matches("\\A\\p{ASCII}*\\z".toRegex())) { appName = appPackageName } - return appName + appName } catch (e: Exception) { Timber.e(e, "## AppNameProvider() : failed") - return "ElementAndroid" + "ElementAndroid" } } } diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt index b488a1af0e..11c3af9394 100644 --- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt +++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt @@ -146,7 +146,7 @@ class DialPadFragment : Fragment() { } private fun poll() { - if (!input.isEmpty()) { + if (input.isNotEmpty()) { input = input.substring(0, input.length - 1) formatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(regionCode) if (formatAsYouType) { diff --git a/vector/src/main/res/layout/dialog_export_e2e_keys.xml b/vector/src/main/res/layout/dialog_export_e2e_keys.xml index 10c9dcd59b..eedaa81857 100644 --- a/vector/src/main/res/layout/dialog_export_e2e_keys.xml +++ b/vector/src/main/res/layout/dialog_export_e2e_keys.xml @@ -1,7 +1,6 @@ Date: Fri, 9 Apr 2021 17:05:09 +0200 Subject: [PATCH 008/230] Small cleanup --- vector/src/debug/res/layout/item_sas_emoji.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/debug/res/layout/item_sas_emoji.xml b/vector/src/debug/res/layout/item_sas_emoji.xml index 53fd448f90..fc56bc1948 100644 --- a/vector/src/debug/res/layout/item_sas_emoji.xml +++ b/vector/src/debug/res/layout/item_sas_emoji.xml @@ -39,7 +39,7 @@ android:layout_height="wrap_content" android:layout_marginStart="8dp" android:textSize="16sp" - tools:text="@string/verification_emoji_wrench" /> + tools:text="@string/verification_emoji_spanner" /> + tools:text="verification_emoji_spanner" /> From a1209c83bb832252101d68b3e5f260c9855c70e6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Apr 2021 19:58:29 +0200 Subject: [PATCH 009/230] New store descriptions --- CHANGES.md | 2 +- .../android/en-US/full_description.txt | 45 +++++++++++-------- .../android/en-US/short_description.txt | 2 +- fastlane/metadata/android/en-US/title.txt | 2 +- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a73551cc33..d946f6ef8a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,7 +23,7 @@ Test: - Other changes: - - + - New store descriptions Changes in Element 1.1.4 (2021-04-09) =================================================== diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index e939b75bb7..853885944c 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,30 +1,39 @@ -Element is a new type of messenger and collaboration app that: +Element is both a secure messenger and a productivity team collaboration app that is ideal for group chats while remote working. This chat app uses end-to-end encryption to provide powerful video conferencing, file sharing and voice calls. -1. Puts you in control to preserve your privacy -2. Lets you communicate with anyone in the Matrix network, and even beyond by integrating with apps such as Slack -3. Protects you from advertising, datamining and walled gardens -4. Secures you through end-to-end encryption, with cross-signing to verify others +Element’s features include: +- Advanced online communication tools +- Fully encrypted messages to allow safer corporate communication, even for remote workers +- Decentralized chat based on the Matrix open source framework +- File sharing securely with encrypted data while managing projects +- Video chats with Voice over IP and screen sharing +- Easy integration with your favourite online collaboration tools, project management tools, VoIP services and other team messaging apps -Element is completely different from other messaging and collaboration apps because it is decentralised and open source. +Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages. -Element lets you self-host - or choose a host - so that you have privacy, ownership and control of your data and conversations. It gives you access to an open network; so you’re not just stuck speaking to other Element users only. And it is very secure. +Privacy and encrypted messaging +Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification. -Element is able to do all this because it operates on Matrix - the standard for open, decentralised communication. +Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack. -Element puts you in control by letting you choose who hosts your conversations. From the Element app, you can choose to host in different ways: +Element can be self-hosted +To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility. +Own your data +You decide where to keep your data and messages. Without the risk of data mining or access from third parties. + +Element puts you in control in different ways: 1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers -2. Self-host your account by running a server on your own hardware +2. Self-host your account by running a server on your own IT infrastructure 3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform -Why choose Element? +Open messaging and collaboration +You can chat with anyone on the Matrix network, whether they’re using Element, another Matrix app or even if they are using a different messaging app. -OWN YOUR DATA: You decide where to keep your data and messages. You own it and control it, not some MEGACORP that mines your data or gives access to third parties. +Super secure +Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signed device verification. -OPEN MESSAGING AND COLLABORATION: You can chat with anyone else in the Matrix network, whether they’re using Element or another Matrix app, and even if they are using a different messaging system of the likes of Slack, IRC or XMPP. +Complete communication and integration +Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done. -SUPER-SECURE: Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signing to verify the devices of conversation participants. - -COMPLETE COMMUNICATION: Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done. - -EVERYWHERE YOU ARE: Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io. +Pick up where you left off +Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 023b366c9a..5a98f6f772 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Secure decentralised chat & VoIP. Keep your data safe from third parties. \ No newline at end of file +Group messenger - encrypted messaging, group chat and video calls \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt index 039da1fc3b..12fa89b99b 100644 --- a/fastlane/metadata/android/en-US/title.txt +++ b/fastlane/metadata/android/en-US/title.txt @@ -1 +1 @@ -Element (previously Riot.im) \ No newline at end of file +Element - Secure Messenger \ No newline at end of file From e404589544f73e2ad3cd6869dd67cc71ee74e565 Mon Sep 17 00:00:00 2001 From: gradle-update-robot Date: Sat, 10 Apr 2021 01:06:51 +0000 Subject: [PATCH 010/230] Update Gradle Wrapper from 6.8.3 to 7.0. Signed-off-by: gradle-update-robot --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6e61ea7487..9d174797f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip +distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From a9ef58912f360309e90a6d51e520fdbf051865a5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 10 Apr 2021 08:45:27 +0200 Subject: [PATCH 011/230] http -> https Fix the followinf error, since gradle 7: `Using insecure protocols with repositories, without explicit opt-in, is unsupported. Switch Maven repository 'maven2(http://dl.bintray.com/piasy/maven)' to redirect to a secure protocol (like HTTPS) or allow insecure protocols. See https://docs.gradle.org/7.0/dsl/org.gradle.api.artifacts.repositories.UrlArtifactRepository.html#org.gradle.api.artifacts.repositories.UrlArtifactRepository:allowInsecureProtocol for more details.` --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b8da6c3864..d645382015 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,7 @@ allprojects { } } maven { - url "http://dl.bintray.com/piasy/maven" + url "https://dl.bintray.com/piasy/maven" content { includeGroupByRegex "com\\.github\\.piasy" } From 91e85f56d88ac1919d0c86d4ad9e2a84590b5bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 7 Apr 2021 17:38:02 +0000 Subject: [PATCH 012/230] Translated using Weblate (Estonian) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 2b4a0e0590..b2165af837 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -2672,4 +2672,5 @@ Saadetud Saadan Jututubade kataloog + Sõnum on saadetud \ No newline at end of file From 166817411cfe3d3d3a7c17fcedbaa8b0b8e9e332 Mon Sep 17 00:00:00 2001 From: random Date: Thu, 8 Apr 2021 13:17:37 +0000 Subject: [PATCH 013/230] Translated using Weblate (Italian) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/it/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/it/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/it/changelogs/40101030.txt diff --git a/fastlane/metadata/android/it/changelogs/40101020.txt b/fastlane/metadata/android/it/changelogs/40101020.txt new file mode 100644 index 0000000000..21057629e3 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: prestazioni migliorate e correzione di errori! +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/it/changelogs/40101030.txt b/fastlane/metadata/android/it/changelogs/40101030.txt new file mode 100644 index 0000000000..a62c4a0736 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: prestazioni migliorate e correzioni di errori! +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From e0a79af93a0f53e5601f2bdd7a9efb33ed07fbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 7 Apr 2021 17:39:24 +0000 Subject: [PATCH 014/230] Translated using Weblate (Estonian) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/et/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/et/changelogs/40101030.txt diff --git a/fastlane/metadata/android/et/changelogs/40101020.txt b/fastlane/metadata/android/et/changelogs/40101020.txt new file mode 100644 index 0000000000..5f34bb579f --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/et/changelogs/40101030.txt b/fastlane/metadata/android/et/changelogs/40101030.txt new file mode 100644 index 0000000000..5a558d911a --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From f4959ba553d043697671675b5bec18a3acded29b Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 7 Apr 2021 18:54:55 +0000 Subject: [PATCH 015/230] Translated using Weblate (Ukrainian) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/uk/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/uk/changelogs/40101030.txt diff --git a/fastlane/metadata/android/uk/changelogs/40101020.txt b/fastlane/metadata/android/uk/changelogs/40101020.txt new file mode 100644 index 0000000000..469de21a6f --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок! +Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/uk/changelogs/40101030.txt b/fastlane/metadata/android/uk/changelogs/40101030.txt new file mode 100644 index 0000000000..da2bb0ddd6 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок! +Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From 931bd1042b41a526a97df03c4cc69e647ea24237 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Fri, 9 Apr 2021 23:23:44 +0000 Subject: [PATCH 016/230] Translated using Weblate (Catalan) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ca/ --- fastlane/metadata/android/ca/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/ca/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/ca/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/ca/changelogs/40101030.txt diff --git a/fastlane/metadata/android/ca/changelogs/40101020.txt b/fastlane/metadata/android/ca/changelogs/40101020.txt new file mode 100644 index 0000000000..43c140214f --- /dev/null +++ b/fastlane/metadata/android/ca/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors! +Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/ca/changelogs/40101030.txt b/fastlane/metadata/android/ca/changelogs/40101030.txt new file mode 100644 index 0000000000..9b2627e7f2 --- /dev/null +++ b/fastlane/metadata/android/ca/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors! +Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From e4c664edafa2fdd593727ae80c04aeb7713dd591 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Thu, 8 Apr 2021 06:27:15 +0000 Subject: [PATCH 017/230] Translated using Weblate (French) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 31eb24371c..92fe2986a4 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2679,4 +2679,5 @@ Afficher tous les salons dans le répertoire, y compris ceux au contenu choquant. Afficher les salons au contenu choquant Répertoire des salons + Message envoyé \ No newline at end of file From 55d4304620a0eac9deb3373d1a18c73b1ca8a24e Mon Sep 17 00:00:00 2001 From: random Date: Thu, 8 Apr 2021 13:16:18 +0000 Subject: [PATCH 018/230] Translated using Weblate (Italian) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index a60946c593..fcb805ef30 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2734,4 +2734,5 @@ \nScaricamento dati… Sinc. iniziale: \nIn attesa di risposta dal server… + Messaggio inviato \ No newline at end of file From 4561dc8c64f12815098cec4dabad83792d3aba99 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Thu, 8 Apr 2021 17:15:41 +0000 Subject: [PATCH 019/230] Translated using Weblate (Czech) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 6d6574a87e..0b76b6c019 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -2723,4 +2723,5 @@ Zobrazit všechny místnosti v adresáři místností, včetně místností s explicitním obsahem. Zobrazit místnosti s explicitním obsahem Adresář místností + Zpráva odeslána \ No newline at end of file From bdb4e2dab839616b188478241fe2360805360b52 Mon Sep 17 00:00:00 2001 From: Jeanne Lavoie Date: Fri, 9 Apr 2021 22:12:02 +0000 Subject: [PATCH 020/230] Translated using Weblate (German) Currently translated at 99.9% (2362 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 52e67eb460..baeb910d84 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2734,4 +2734,5 @@ Alle fehgeschlagene Nachrichten löschen Senden der Nachricht gescheitert Wird gesendet + Nachricht gesendet \ No newline at end of file From ceca81f0ab2f234ba20bba56b09b076422c410f6 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 9 Apr 2021 05:27:34 +0000 Subject: [PATCH 021/230] Translated using Weblate (Russian) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/ --- fastlane/metadata/android/ru/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/ru/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/ru/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/ru/changelogs/40101030.txt diff --git a/fastlane/metadata/android/ru/changelogs/40101020.txt b/fastlane/metadata/android/ru/changelogs/40101020.txt new file mode 100644 index 0000000000..70e164f39d --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: улучшение производительности и исправления ошибок! +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/ru/changelogs/40101030.txt b/fastlane/metadata/android/ru/changelogs/40101030.txt new file mode 100644 index 0000000000..381c2761d0 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: улучшение производительности и исправления ошибок! +Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From 007134da12a18cd94adcc18f481315df59161328 Mon Sep 17 00:00:00 2001 From: Vivek K J Date: Fri, 9 Apr 2021 13:41:23 +0000 Subject: [PATCH 022/230] Translated using Weblate (Malayalam) Currently translated at 26.6% (629 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ml/ --- vector/src/main/res/values-ml/strings.xml | 55 ++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index e11aaa7639..2cef400e8e 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -359,7 +359,7 @@ നിങ്ങൾക്ക് ഒരു വിഡിയോ കോൾ ആരംഭിക്കണമെന്ന് ഉറപ്പാണോ\? തിരിച്ചറിയൽ സെർവറിന്റെ URL കഴിയുമെങ്കിൽ, വിവരണം ഇംഗ്ലീഷിൽ എഴുതുക. - ആളുകളെ ഫിൽറ്റർ + ആളുകളെ അരിക്കുക കീ ബാക്കപ്പ് ഉപയൊഗിക്കുക കീ ബാക്കപ്പ് പൂർത്തിയായിട്ടില്ല, കാത്തിരിക്കൂ… ഫോൺ നമ്പറുകൾ @@ -640,4 +640,57 @@ %1$s മുറിയിൽ ചേരാൻ %2$s ന് ക്ഷണം അയച്ചു നിങ്ങളുടെ പ്രൊഫൈൽ %1$s നവീകരിച്ചു %1$s അവരുടെ പ്രൊഫൈൽ %2$s നവീകരിച്ചു + നിഷ്‌ക്രിയം + + %dദി + %dദി + + + %dമ + %dമ + + + %dമി + %dമി + + + %dസെ + %dസെ + + പ്രിവ്യൂ + ഹോൾഡ് + പുനരാരംഭിക്കുക + ചെറുത് + ഇടത്തരം + വലുത് + യഥാർത്ഥം + മുന്നിലുള്ള + പിന്നിലുള്ള + ഹെഡ്‌സെറ്റ് + വായിക്കുക + കമ്മ്യൂണിറ്റികൾ + കമ്മ്യൂണിറ്റികൾ + പിശക് + അപ്രാപ്തമാക്കുക + അവലോകനം + ഉപേക്ഷിക്കുക + താൽക്കാലികമായി നിർത്തുക + വിച്ഛേദിക്കുക + അസാധുവാക്കുക + ഫോർവേഡ് + ഉദ്ധരണി + തുടരുക + വിടുക + + ഈ മുറിക്കായി %1$s എന്ന ഇതര വിലാസം നിങ്ങൾ ചേർത്തു. + ഈ മുറിക്കായി %1$s എന്ന ഇതര വിലാസങ്ങൾ നിങ്ങൾ ചേർത്തു. + + + %1$s ഈ മുറിക്കായി %2$s എന്ന ഇതര വിലാസം ചേർത്തു. + %1$s ഈ മുറിക്കായി %2$s എന്നീ ഇതര വിലാസങ്ങൾ ചേർത്തു. + + ഈ മുറിയുടെ പ്രധാന വിലാസം നിങ്ങൾ നീക്കംചെയ്തു. + %1$s ഈ മുറിയുടെ പ്രധാന വിലാസം നീക്കംചെയ്‌തു. + ഈ മുറിയുടെ പ്രധാന വിലാസം %1$s ആയി നിങ്ങൾ സജ്ജമാക്കി. + സന്ദേശം അയച്ചു \ No newline at end of file From f68eae58918d4b64252d1c58f6c28aef920a6d0c Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Fri, 9 Apr 2021 05:28:14 +0000 Subject: [PATCH 023/230] Translated using Weblate (Russian) Currently translated at 99.4% (2351 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 1ed1b17734..dc8387733c 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -2820,4 +2820,5 @@ \nЗагрузка данных… Начальная синхронизация: \nОжидание ответа сервера… + Сообщение отправлено \ No newline at end of file From a7be6a61efc99389df7c75df719bff59a2f18e40 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 8 Apr 2021 03:28:30 +0000 Subject: [PATCH 024/230] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-Hant/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/zh-Hant/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/40101030.txt diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101020.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101020.txt new file mode 100644 index 0000000000..90e76b074e --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/40101020.txt @@ -0,0 +1,2 @@ +此版本中的主要變更:效能改進與錯誤修復! +完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101030.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101030.txt new file mode 100644 index 0000000000..c13d6ecfd4 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/40101030.txt @@ -0,0 +1,2 @@ +此版本中的主要變更:效能改進與錯誤修復! +完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.3 From fad3a60bc4311b0f001c26189ff16aea06c96358 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Thu, 8 Apr 2021 06:28:05 +0000 Subject: [PATCH 025/230] Translated using Weblate (French) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/fr/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fr/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/fr/changelogs/40101030.txt diff --git a/fastlane/metadata/android/fr/changelogs/40101020.txt b/fastlane/metadata/android/fr/changelogs/40101020.txt new file mode 100644 index 0000000000..7bce8ac19a --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : amélioration des performances et corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/fr/changelogs/40101030.txt b/fastlane/metadata/android/fr/changelogs/40101030.txt new file mode 100644 index 0000000000..93f0b9e7fb --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : amélioration des performances et corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.3 From 6443dd6c4dd6c74236b3a9eb5928f72d2709ce9a Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Fri, 9 Apr 2021 23:22:39 +0000 Subject: [PATCH 026/230] Translated using Weblate (Catalan) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- vector/src/main/res/values-ca/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index d5a9ed5b3e..812cb4b531 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -2740,4 +2740,5 @@ Ha fallat Enviat Enviant + Missatge enviat \ No newline at end of file From 67cbe6ebb07d37f3d0c58fb6cd012b8236a9cadd Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 9 Apr 2021 16:13:37 +0000 Subject: [PATCH 027/230] Translated using Weblate (Albanian) Currently translated at 99.4% (2351 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- vector/src/main/res/values-sq/strings.xml | 69 ++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 90b0e45a9b..ef68551dc8 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -1623,7 +1623,7 @@ \nJu lutemi, lejoni, te flluska pasuese, hyrje për të qenë e mundur të eksportohen kyçet tuaj dorazi. Tani për tani s’la lidhje rrjeti Ripohoni fjalëkalimin tuaj - Këtë s’e bëni dot që nga ${app_name}-i për celular + Këtë s’e bëni dot që nga ${app_name} për celular Lypset mirëfilltësim Integrime Përdorni një Përgjegjës Integrimesh që të administroni robotë, ura, widget-e dhe paketa ngjitësish. @@ -2595,4 +2595,71 @@ Rimerre E paautorizuar, i mungojnë kredenciale të vlefshme mirëfilltësimi Parazgjedhje Sistemi + Jeni i sigurt se doni të fshihen krejt mesazhet e padërguar në këtë dhomë\? + Fshi mesazhet e padërguar + Dështoi dërgimi i mesazheve + Doni të anulohet dërgimi i mesazhit\? + Fshiji krejt mesazhet e dështuara + I dështuar + I dërguar + Po dërgohet + Lëndë akti + Akti i gjendjes u dërgua! + Akti u dërgua! + Akt i keqformuar + Lloj mesazhi që mungon + S’ka lëndë + Lëndë Akti + Kyç Gjendjesh + Lloj + Dërgoni Akt Vetjak Gjendjeje + Përpunoni Lëndën + Akte Gjendjeje + Dërgoni Akt Gjendjeje + Dërgoni Akt Vetjak + Eksploroni Gjendje Dhome + Mjete Zhvilluesi + Shihni dëftesa leximi + Mos njofto + Njofto pa tingull + Njofto me tingull + Mesazhi s’u dërgua, për shkak të një gabimi + Mbyllni Zgjedhës emoji-sh + Hap Zgjedhës emoji-sh + Shkallë besimi I besuar + Shkallë besimi Kujdes + Shkallë besimi parazgjedhje + Të përzgjedhura + Video + Kjo dhomë ka skicë të padërguar + Disa nga mesazhet s’janë dërguar + Fshije avatarin + Ndërroni avatarin + Figurë + Importo kyç prej kartele + Hap widget-e + Foto ekrani + + %d zë + %d zëra + + Kufiri është i panjohur. + Shërbyesi juaj Home pranon bashkëngjitje (kartela, media, etj.) me një madhësi deri në %s. + Kufi shërbyesi për ngarkim kartelash + Version shërbyesi + Emër shërbyesi + Rregullime dhome + Të braktiset konferenca e tanishme dhe të kalohet te një tjetër\? + Version dhome + Shfaq krejt dhomat te drejtoria e dhomave, përfshi dhomat me lëndë troç. + Shfaq dhoma me lëndë eksplicite + Drejtori dhomash + Vlerë e re + Kthehuni + Ndërroje + Mesazh i dërguar + Njëkohësimi Fillestar: +\nPo shkarkohen të dhëna… + Njëkohësimi Fillestar: +\nPo pritet për përgjigje nga shërbyesi… \ No newline at end of file From f0e75d06346e782b3eab738a7f706d196ef55abd Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 8 Apr 2021 03:25:38 +0000 Subject: [PATCH 028/230] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index f9345b146f..bd628d9bdb 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2621,4 +2621,5 @@ 顯示聊天室目錄中的所有聊天室,包含有明確內容的聊天室。 顯示帶有明確內容的聊天室 聊天室目錄 + 訊息已傳送 \ No newline at end of file From 66db67e8571a3f3edfa7de2438e5b4a99f8d3704 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 10 Apr 2021 16:43:16 +0100 Subject: [PATCH 029/230] Missed a spot Signed-off-by: Dominic Fischer --- .../src/main/java/org/matrix/android/sdk/rx/RxSession.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index cac0a32232..0fe2b01576 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -141,8 +141,8 @@ class RxSession(private val session: Session) { } fun getRoomIdByAlias(roomAlias: String, - searchOnServer: Boolean): Single> = singleBuilder { - session.getRoomIdByAlias(roomAlias, searchOnServer, it) + searchOnServer: Boolean): Single> = rxSingle { + session.getRoomIdByAlias(roomAlias, searchOnServer) } fun getProfileInfo(userId: String): Single = rxSingle { From 9ce12a1b283686644d3201999b066dfb8e2ebc84 Mon Sep 17 00:00:00 2001 From: Ariko Date: Sat, 10 Apr 2021 16:15:51 +0000 Subject: [PATCH 030/230] Translated using Weblate (French) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 92fe2986a4..1bb02454ec 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2629,13 +2629,13 @@ %s a mis l’appel en attente Mettre en attente Reprendre - Non autorisé, informations d’authentification manquantes + Non autorisé, identifiants d\'authentification valides manquants Nouvelle valeur Revenir Dé-publier Changer Ajouter - Comme le système + Par défaut du système Vous avez modifié les adresses de ce salon. %1$s a modifié les adresses de ce salon. Vous avez modifié les adresses principale et alternative de ce salon. From 759528f19a543b6899caf8de65415e1e42f5cad1 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 11 Apr 2021 10:54:17 +0100 Subject: [PATCH 031/230] Missed another spot Signed-off-by: Dominic Fischer --- .../java/im/vector/app/features/home/HomeDetailViewModel.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index c87b19f0e6..908270cb79 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -109,9 +109,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho ) .map { it.roomId } try { - awaitCallback { - session.markAllAsRead(roomIds, it) - } + session.markAllAsRead(roomIds) } catch (failure: Throwable) { Timber.d(failure, "Failed to mark all as read") } From 35667db29c3c2a82cee53edcea9940d3ebec543b Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 11 Apr 2021 11:35:28 +0100 Subject: [PATCH 032/230] Lint --- .../main/java/im/vector/app/features/home/HomeDetailViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 908270cb79..d6a8b075f4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -35,7 +35,6 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber From 4fcbf718caacddcca563cce0051998674c6c1041 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Sun, 11 Apr 2021 12:05:42 +0000 Subject: [PATCH 033/230] Translated using Weblate (French) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- .../metadata/android/fr/full_description.txt | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/fastlane/metadata/android/fr/full_description.txt b/fastlane/metadata/android/fr/full_description.txt index 2b17d8f846..7604384dd1 100644 --- a/fastlane/metadata/android/fr/full_description.txt +++ b/fastlane/metadata/android/fr/full_description.txt @@ -1,30 +1,28 @@ -Element est une nouvelle application de messagerie et de collaboration qui : +Element est une nouvelle application de messagerie et de collaboration qui : -1) Vous place aux commandes de votre vie privée -2) Vous permet de communiquer avec n'importe qui du réseau Matrix, et plus encore par des intégrations d'autres applications comme Slack ou Discord -3) Vous protège de la publicité et de la collecte de données -4) Vous sécurise grâce à du chiffrement bout-à-bout, avec de la signature croisée pour authentifier les autres utilisateurs +1. Vous permet de préserver votre vie privée +2. Vous permet de communiquer avec n’importe qui sur réseau Matrix, et plus encore grâce aux intégrations d’autres applications telles que Slack ou Discord +3. Vous protège de la publicité et de la collecte de données +4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs -Element est complètement différent des autres applications de messagerie et de collaboration puisque l'application est décentralisée et open-source. +Element est complètement différente des autres applications de messagerie et de collaboration puisque l’application est décentralisée et open-source. -Element vous permet d'héberger vous-même -ou de choisir un hôte- vous permettant d'assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous offre l'accès à un réseau ouvert, vous n'êtes donc pas condamné à parler à d'autres utilisateurs d'Element seulement. Et c'est très sécurisé. +Element vous permet d’héberger vous-même ou de choisir un hôte vous permettant d’assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous donne accès à un réseau ouvert. Vous n’êtes donc pas condamné à parler à d’autres utilisateurs d’Element seulement. Et c'est très sécurisé. -Element peut faire tout ça car il est basé sur Matrix, le protocole standard pour la communication ouverte et décentralisée. +Element peut faire tout ça car elle est basée sur Matrix, le protocole standard pour la communication ouverte et décentralisée. -Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières : +Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières : -1) Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parler les milliers de serveurs public hébergés par des bénévoles -2) Héberger vous-même votre compte en installant un serveur sur votre propre machine -3) Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS) +1. Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parmi les milliers de serveurs public hébergés par des bénévoles +2. Héberger vous-même votre compte en installant un serveur sur votre propre machine +3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS) -Pourquoi choisir Element ? +VOS DONNÉES VOUS APPARTIENNENT : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant. -POSSÉDEZ VOS DONNÉES : Vous décidez où conserver vos données et vos messages. Vous les possédez et vous les contrôlez, et non une MEGACORP qui mine vos données ou les donnent à des tiers +MESSAGERIE ET COLLABORATION OUVERTES : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP. -UNE MESSAGERIE OUVERTE ET COLLABORATIVE : Vous pouvez discuter avec n'importe qui sur le réseau Matrix, qu'ils utilisent Element ou une autre application basée sur Matrix, et même s'ils utilisent un système de messagerie différent comment Slack, Discord, IRC ou XMPP. +ULTRA SÉCURISÉ : chiffrement de bout en bout (seuls les membres d’une conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs. -SUPER SÉCURISÉ : Un réel chiffrement bout-à-bout (seulement ceux deux la conversation peuvent déchiffrer les messages), et une signature croisée pour vérifier les appareils des participants de la conversation. +TOUTES VOS COMMUNICATIONS : messagerie, appels audio et vidéo, partage de fichier, partage d’écran et un grand nombre d’intégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets. -COMMUNICATION COMPLÈTE : Messagerie, appels vocaux et vidéo, transfert de fichiers, partage d'écran et un tas d'intégrations, robots et widgets. Construisez des salons, des communautés, restez en contact et accomplissez de grandes choses. - -PARTOUT OÙ VOUS ÊTES : Restez connectés peu import où vous êtes avec la synchronisation complète de l'historique des messages sur tous vos appareils et sur le web sur https://app.element.io. +PARTOUT AVEC VOUS : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app. From fbb77b7332856c637248a550e5c60eefb49b3e32 Mon Sep 17 00:00:00 2001 From: Jeanne Lavoie Date: Sat, 10 Apr 2021 17:06:17 +0000 Subject: [PATCH 034/230] Translated using Weblate (German) Currently translated at 99.9% (2362 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index baeb910d84..c60e226b7b 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -599,8 +599,8 @@ Möchtest du alle Nachrichten dieses Nutzers verbergen\? \n \nBeachte: Diese Aktion wird die App neu starten und einige Zeit brauchen. - Upload abbrechen - Download abbrechen + Hochladen abbrechen + Herunterladen abbrechen Suchen Raum-Mitglieder filtern @@ -1945,7 +1945,7 @@ Eine Person %1$d Personen - Uploads + Hochgeladene Dateien Raum verlassen Verlasse den Raum… Administratoren @@ -2615,7 +2615,7 @@ Raum-Berechtigungen Du hast nicht die Berechtigung zum Aktualisieren der Rollen, die zum Ändern verschiedener Teile des Raums erforderlich sind Dieser Raum ist nicht öffentlich. Du wirst ihn ohne Einladung nicht wieder betreten können. - Standard Design + Standard-Design Oder Sendet Schnee ❄️ Sendet Konfetti 🎉 From c7a241fbf54e7bad60824a2371930e64b1db8748 Mon Sep 17 00:00:00 2001 From: Jeanne Lavoie Date: Sat, 10 Apr 2021 17:17:25 +0000 Subject: [PATCH 035/230] Translated using Weblate (French (Canada)) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr_CA/ --- vector/src/main/res/values-fr-rCA/strings.xml | 2674 ++++++++++++++++- 1 file changed, 2673 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml index 0d2c4cc409..cf3e08d791 100644 --- a/vector/src/main/res/values-fr-rCA/strings.xml +++ b/vector/src/main/res/values-fr-rCA/strings.xml @@ -1,4 +1,2676 @@ - + Vous avez modifié le gadget logiciel %1$s + %1$s a modifié le gadget logiciel %2$s + Vous avez supprimé le gadget logiciel %1$s + %1$s a supprimé le gadget logiciel %2$s + Vous avez ajouté le gadget logiciel %1$s + %1$s a ajouté le gadget logiciel %2$s + Aucun gadget logiciel n’est activé + Échec de la suppression du gadget logiciel + Échec de l’ajout du gadget logiciel + Impossible de créer le gadget logiciel. + Ce gadget logiciel veut utiliser les ressources suivantes : + L’identifiant du gadget logiciel + Recharger le gadget logiciel + Échec de chargement du gadget logiciel. +\n%s + Ce gadget logiciel a été ajouté par : + Charger un gadget logiciel + Voulez-vous vraiment supprimer le gadget logiciel de ce salon \? + La création du gadget logiciel a échoué + Ouvrir les gadgets logiciels + Modifier les gadgets logiciels + Utiliser des robots, passerelles, gadgets logiciels et jeux d’autocollants + Vérification annulée + Vérifiez vos appareils depuis les paramètres. + Un des éléments suivants est peut-être compromis : +\n +\n- Votre mot de passe +\n- Votre serveur d’accueil +\n- Cet appareil ou l’autre appareil +\n- La connexion internet utilisée par un des deux appareils +\n +\nNous vous recommandons de modifier immédiatement votre mot de passe et votre clé de récupération dans les paramètres. + Vous ne vérifierez pas %1$s (%2$s) si vous annulez maintenant. Recommencez sur son profil utilisateur. + Si vous annulez, vous ne pourrez pas lire les messages chiffrés sur votre nouvel appareil, et les autres utilisateurs ne lui feront pas confiance + Si vous annulez, vous ne pourrez pas lire les messages chiffrés sur cet appareil, et les autres utilisateurs ne lui feront pas confiance + Votre compte est peut-être compromis + Ce n’était pas moi + Utilisez cette session pour vérifiez la nouvelle, ce qui lui permettra d’avoir accès aux messages chiffrés. + Appuyer pour examiner et vérifier + Nouvelle connexion. Était-ce vous \? + Actualiser + Déverrouiller l’historique des messages chiffrés + Exporter le rapport d’audit + Demandes de clé + ${app_name} Android + Les clés sont déjà à jour ! + Évènement modéré par l’administrateur du salon, motif : %1$s + Évènement supprimé par l’utilisateur, motif : %1$s + Motif de la suppression + Fournir un motif + Voulez-vous vraiment supprimer cet évènement \? Notez que si vous supprimez un changement de nom ou de sujet du salon, cela pourrait annuler le changement. + Confirmer la suppression + Voulez-vous envoyer cette pièce jointe à %1$s \? + Supprimer… + Vous devriez accéder à votre coffre secret uniquement depuis un appareil de confiance + Attention : + Saisir la phrase secrète du coffre secret + Impossible de trouver les secrets dans le stockage + Nouvelle connexion + Si vous n’avez pas accès à une session existante + Utiliser une phrase secrète ou une clé de récupération + Crée un sondage simple + Option sélectionnée + Supprimer les données du compte de type %1$s \? +\n +\nÀ utiliser avec précaution, ceci peut entraîner des comportements inattendus. + Données du compte + Outils de développement + Le mode avion est activé + La connexion avec le serveur a été perdue + Non + Oui + On y est presque ! Est-ce que %s affiche le même bouclier \? + Code QR + Réinitialiser les clés + Initialiser la signature croisée + Tant que cet utilisateur ne fera pas confiance à cette session, les messages qu’elle envoie et qui lui sont envoyés seront affichés avec des avertissements. Sinon, vous pouvez la vérifier manuellement. + %1$s (%2$s) s’est connecté en utilisant une nouvelle session : + Cette session est fiable pour la messagerie sécurisée car %1$s (%2$s) l’a vérifiée : + Non fiable + Fiable + Sessions + Échec lors de la récupération des sessions + Attention + Vérifié + Vérifier + Utilisez une session existante pour vérifier celle-ci, ce qui lui permettra d’avoir accès aux messages chiffrés. + Améliorer la sécurité + Les autres utilisateurs ne lui font peut-être pas confiance + Vérifier cette session + Vérifiez cette session pour la marquer comme fiable et lui permettre d’accéder aux messages chiffrés. Si vous ne vous êtes pas connecté à cette session, votre compte est peut-être compromis : + Cette session est fiable pour la messagerie sécurisée car vous l’avez vérifiée : + Aucune information cryptographique n’est disponible + La limite n’est pas connue. + Votre serveur d’accueil accepte les pièces jointes (fichiers, médias, etc.) jusqu’à %s. + Version du serveur + Nom du serveur + Se déconnecter de cette session + Gérer les sessions + Afficher toutes les sessions + Sessions actives + L\'administrateur de votre serveur a désactivé le chiffrement de bout en bout par défaut dans les salons et conversations privés. + La signature croisée n’est pas activée + La signature croisée est activée. +\nLes clés ne sont pas fiables + La signature croisée est activée +\nLes clés sont fiables. +\nLes clés privées ne sont pas connues + La signature croisée est activée +\nLes clés privées sont sur l’appareil. + Signature croisée + À présent, votre nouvelle session est vérifiée. Elle a accès à vos messages chiffrés et les autre utilisateurs la verront comme fiable. + Les messages avec cet utilisateur sont chiffrés de bout en bout et ne peuvent pas être lus par des tiers. + Comparez le code avec celui qui s’affiche sur l’écran de l’autre utilisateur. + Comparez les émojis uniques, en vous assurant qu’ils apparaissent dans le même ordre. + Pour plus de sécurité, faites cela en personne ou utilisez un autre moyen de communication. + Pour plus de sécurité, vérifiez %s en comparant un code à usage unique. + Activer le chiffrement + Une fois qu’il est activé, le chiffrement ne peut pas être désactivé. Les messages envoyés dans les salons chiffrés ne peuvent pas être vus par le serveur, uniquement par les participants du salon. Activer le chiffrement empêchera peut-être les robots et les passerelles de fonctionner correctement. + Activer le chiffrement \? + Une fois qu’il est activé, le chiffrement ne peut pas être désactivé. + Vous n\'avez pas le droit d’activer le chiffrement dans ce salon. + Active le chiffrement de bout en bout… + Éditeur de messages + Fil de discussion + Envoie la réaction fournie colorée comme un arc-en-ciel + Envoie le message fourni coloré comme un arc-en-ciel + Autres salons + Salons récents + Cette session est incapable de partager cette vérification avec vos autres sessions. +\nLa vérification sera sauvegardée localement et partagée dans une version future de l’application. + Ne plus ignorer + ${app_name} a rencontré un problème lors de l’affichage du contenu de l’évènement ayant pour identifiant « %1$s » + ${app_name} ne gère pas les messages de type « %1$s » + ${app_name} ne gère pas les évènements de type « %1$s » + Aller à l’accusé de lecture + Conversation privée + Personnalisé (%1$d) dans %2$s + Par défaut dans %1$s + Modérateur dans %1$s + Administrateur dans %1$s + Utilisateurs + Invitations + Personnalisé + Modérateurs + Administrateurs + En train de quitter le salon… + Quitter + Quitter le salon + Notifications + Paramètres + Paramètres du salon + Actions d’administrateur + Plus + En savoir plus + Sécurité + Les messages ici sont chiffrés de bout en bout. +\n +\nVos messages sont sécurisés avec des verrous et seuls vous et le destinataire en possédez la clé unique pour les déverrouiller. + Les messages dans ce salon sont chiffrés de bout en bout. +\n +\nVos messages sont sécurisés avec des verrous et seuls vous et le destinataire avez les clés uniques pour les déverrouiller. + Les messages ici ne sont pas chiffrés de bout en bout. + Les messages dans ce salon ne sont pas chiffrés de bout en bout. + Pour une sécurité supplémentaire, vérifiez %s en comparant un code à usage unique sur vos deux appareils. +\n +\nPour une sécurité optimale, faites-le en personne. + Nous attendons %s… + %s a été vérifié + Vérifier %s + Image de code QR + Si vous ne pouvez pas scanner le code ci-dessus, procédez à la vérification en comparant une sélection courte et unique d’émojis. + Vérifier avec des émojis + Vérifier en comparant des émojis + Si vous ne pouvez pas vous voir en personne, comparez des émojis à la place + Impossible de scanner + Scanner leur code + Scanner le code avec l’appareil de l’autre utilisateurs pour vous vérifier réciproquement de façon sécurisée + Vous + Vérifier manuellement + Vérifier cette session + Demande de vérification + Vérification envoyée + Vous avez accepté + %s a accepté + Vous avez annulé + %s a annulé + En attente… + Conclusion de la vérification + A réagi avec : %s + Boutons de robot + Sondage + Autocollants + Fichier + Audio + Image. + Vidéo. + Un des éléments suivants est peut-être compromis : +\n +\n - Votre serveur d’accueil +\n - Le serveur d’accueil auquel est connecté l’utilisateur que vous essayez de vérifier +\n - Votre connexion internet ou celle de l’autre utilisateur +\n - Votre appareil ou celui de l’autre utilisateur + Non sécurisé + Cherchez le bouclier vert pour vous assurer qu’un utilisateur est fiable. Faites confiance à tous les utilisateurs d’un salon pour vous assurer que le salon est fiable. + Pour une sécurité optimale, utilisez un autre moyen de communication ou faites cela en personne. + Vérifiez cet utilisateur en confirmant que les émojis uniques suivants apparaissent sur son écran, dans le même ordre. + Ils ne correspondent pas + Ils correspondent + Connexion non fiable + Création du salon… + Certains caractères sont interdits + Veuillez fournir une adresse de salon + Cette adresse est déjà utilisée + Adresse du salon + Vous devriez l’activer si le salon n’est utilisé que pour collaborer avec des équipes internes sur votre serveur d’accueil. Ceci ne peut pas être changé plus tard. + Empêcher les personnes qui ne sont pas membres de %s de rejoindre ce salon + Masquer les paramètres avancés + Afficher les paramètres avancés + Une fois qu’il est activé, le chiffrement ne peut pas être désactivé. + Activer le chiffrement + Préfixe ¯\\_(ツ)_/¯ à un message en texte brut + ${app_name} peut planter plus souvent quand une erreur inattendue survient + Défaillance rapide + Seuls les premiers résultats sont affichés, saisissez plus de lettres… + Autres sessions + Cette session + Paramètres + Secousse détectée ! + Secouez votre téléphone pour tester le seuil de détection + Seuil de détection + Secouer de frustration + Le mode développeur active des fonctionnalités cachées et peut rendre l’application moins stable. Réservé aux développeurs ! + Mode développeur + Paramètres avancés + Voir toutes mes sessions + Synchronisation initiale… + La description est trop courte + Votre lien matrix.to était malformé + La session en cours est celle de l’utilisateur %1$s et vous fournissez des identifiants pour l’utilisateur %2$s. Ce n’est pas pris en charge par ${app_name}. +\nEffacez d’abord les données, puis reconnectez-vous avec un autre compte. + Effacer les données + Vous perdrez l’accès à vos messages sécurisés sauf si vous vous connectez pour récupérer vos clés de chiffrement. + Effacer toutes les données stockées sur cet appareil \? +\nReconnectez-vous pour accéder aux données et aux messages de votre compte. + Effacer les données + Effacer toutes les données + Attention : Vos données personnelles (y compris les clés de chiffrement) sont toujours stockées sur cet appareil. +\n +\nEffacez-les si vous n’utilisez plus cet appareil ou si vous voulez vous connecter à un autre compte. + Effacer les données personnelles + Mot de passe + Se connecter + Connectez-vous pour récupérer les clés de chiffrement stockées uniquement sur cet appareil. Vous en avez besoin pour lire tous vos messages sécurisés sur n’importe quel appareil. + L’administrateur de votre serveur d’accueil (%1$s) vous a déconnecté de votre compte %2$s (%3$s). + Se connecter + Vous êtes déconnecté + Se reconnecter + Cela peut être dû à plusieurs raisons : +\n +\n• Vous avez changé votre mot de passe sur une autre session. +\n +\n• Vous avez supprimé cette session depuis une autre session. +\n +\n• L’administrateur de votre serveur a invalidé votre accès pour des raisons de sécurité. + Vous êtes déconnecté + Vu par + Impossible de trouver un serveur d’accueil valide. Veuillez vérifier votre identifiant + Ce n’est pas un identifiant d’utilisateur valide. Format attendu : « @utilisateur:serveuraccueil.org » + Si vous ne connaissez pas votre mot de passe, faites précédent et réinitialisez-le. + %1$s à %2$s + FICHIERS + Il n’y a aucun média dans ce salon + MÉDIA + %1$d de %2$d + Impossible de traiter les données de partage + Pivoter et rogner + Autocollants + Galerie + Audio + Appareil photo + Contact + Fichier + Ajouter une image à partir de + Une erreur est survenue pendant la récupération de la pièce jointe. + %s a lu + %1$s et %2$s ont lu + %1$s, %2$s et %3$s ont lu + Sauter en bas de page + Masquer le mot de passe + Afficher le mot de passe + Fermer la bannière de sauvegarde des clés + Créer un nouveau salon + Créer une nouvelle conversation privée en scannant un code QR + Créer une nouvelle conversation privée avec un identifiant Matrix + Créer une nouvelle conversation privée + Fermer le menu de création de salon… + Ouvrir le menu de création de salon + Ouvrir le menu de navigation + Envoyer une pièce jointe + On dirait que le serveur met trop de temps à répondre. Ça peut être dû à une mauvaise connexion ou à une erreur avec le serveur. Réessayez plus tard. + Réessayez quand vous aurez accepté les termes et conditions de votre serveur d’accueil. + Les journaux verbeux aideront les développeurs en fournissant plus de journaux quand vous envoyez un rapport d’anomalie. Même si cette option est activée, l’application n’envoie pas le contenu des messages ou toute autre donnée personnelle. + Activer les journaux verbeux. + Le code de vérification n’est pas correct. + Code + Un SMS a été envoyé à %s. Saisissez le code de vérification qu’il contient. + Le serveur d’identité qui vous avez choisi n’a pas de conditions de service. Continuez uniquement si vous faites confiance au propriétaire de ce service + Le serveur d’identité n’a pas de conditions de service + Veuillez renseigner l’URL du serveur d’identité + Impossible de se connecter au serveur d’identité + Renseignez l’URL d’un serveur d’identité + Autoriser + Révoquer mon autorisation + En attente + Numéros de téléphone découvrables + Les options de découverte apparaîtront quand vous aurez ajouté un numéro de téléphone. + Vous n’utilisez actuellement aucun serveur d’identité. Pour découvrir et être découvrable par les contacts existants que vous connaissez, configurez-en un ci-dessous. + Vous utilisez actuellement %1$s pour découvrir et être découvrable par les contacts existants que vous connaissez. + Modifier le serveur d’identité + Configurer le serveur d’identité + Se déconnecter du serveur d’identité + Serveur d’identité + Lu à + Être découvrable par les autres + Examiner les conditions + Conditions de service + Voir l’historique des éditions + En train de rejoindre le salon… + Suggestions + Contacts + Utilisateurs connus + Récent + Filtrer par nom d’utilisateur ou identifiant… + Entrez du texte pour avoir des résultats + Aucun résultat trouvé, utilisez Ajouter par identifiant matrix pour chercher sur le serveur. + Création du salon… + code QR + Ajouter avec un code QR + Ajouter par identifiant matrix + Lien copié dans le presse-papiers + Ajouter un onglet dédié aux notifications non-lues sur l’écran principal. + Activer le balayage pour répondre dans l’historique + Chercher par nom ou identifiant + Nom ou identifiant (#exemple:matrix.org) + Voir le répertoire des salons + Envoyer un nouveau message privé + Créer un nouveau salon + Vous ne trouvez pas ce que vous cherchez \? + Filtrer les conversations… + Aucune modification trouvée + Modifications de message + (modifié) + Le fichier %1$s a été téléchargé ! + Téléchargement du fichier %1$s… + Envoi du fichier (%1$s / %2$s) + Chiffrement du fichier… + Envoi de la miniature (%1$s / %2$s) + Chiffrement de la miniature… + Attente… + Conversations privées + Afficher l’historique dans les salons chiffrés + Afficher les évènements cachés dans les discussions + Échec d’envoi de la suggestion (%s) + Merci, la suggestion a bien été envoyée + Décrivez votre suggestion ici + Saisissez votre suggestion ci-dessous. + Faire une suggestion + Inscrire le jeton + Aide & à propos + Voix & vidéo + Format : + URL : + session_name : + app_display_name : + push_key : + app_id : + Aucune passerelle de notification enregistrée + Aucune règle de notification définie + Règles de notification + Expert + Sécurité & vie privée + Préférences + Général + Réactions rapides + Vous êtes déjà en train de visualiser ce salon ! + Licences tierces + Version du SDK de Matrix + Importer les clés de chiffrement depuis le fichier « %1$s ». + Une erreur est survenue lors de la récupération des données de sauvegarde de clés + Une erreur est survenue lors de la récupération des informations de confiance + Le salon a été créé, mais certaines invitations n’ont pas été envoyées pour la raison suivante : +\n +\n%s + Publier ce salon dans l’annuaire des salons + Annuaire des salons + Tout le monde pourra rejoindre ce salon + Public + Paramètres du salon + Sujet + Sujet du salon (facultatif) + Nom + Nom du salon + CRÉER + Nouveau salon + Conversations privées + Salons + Impossible d’afficher un aperçu du salon. Voulez-vous le rejoindre \? + Ce salon n’est pas accessible en ce moment. +\nRéessayez plus tard, ou demandez à un administrateur de ce salon de vérifier que vous pouvez y accéder. + L’aperçu des salons visibles par tout le monde n’est pas encore pris en charge par ${app_name} + Impossible d’avoir un aperçu de ce salon + Toutes les communautés + Veuillez patienter… + Changer de réseau + Changer + Aucun réseau. Vérifiez votre connexion Internet. + Créer un nouveau salon + Évènement malformé, affichage impossible + Dernière édition par %1$s le %2$s + Évènement modéré par l’administrateur du salon + Évènement supprimé par l’utilisateur + Afficher un remplaçant pour les messages supprimés + Afficher les messages supprimés + Message supprimé + Réactions + Voir les réactions + Ajouter une réaction + J’aime + D’accord + Réactions + Vos salons seront affichés ici. Appuyez sur le « + » en bas à droite pour trouver ceux existant ou en créer de nouveaux. + Salons + Vos conversations privées seront affichées ici. Appuyez sur « + » en bas à droite pour en démarrer une. + Discussions + Rattrapez votre retard sur vos messages non lus ici + Bienvenue chez vous ! + Vous n’avez plus de messages non lus + Vous êtes à jour ! + Invité par %s + Vous a envoyé une invitation + Rejoignez un salon pour commencer à utiliser l’application. + Réessayer + Répondre + Modifier + Il semblerait que vous essayez de vous connecter à un autre serveur d’accueil. Voulez-vous vous déconnecter \? + Aucun serveur d’identité n’est configuré, il est nécessaire pour réinitialiser votre mot de passe. + Vous n’utilisez aucun serveur d’identité + Erreur inconnue + Vous avez besoin de la permission pour gérer les gadgets logiciels de ce salon + Utilisez un gestionnaire d’intégrations pour gérer les robots, les passerelles, les gadgets logiciels et les jeux d’autocollants. +\nLes gestionnaires d’intégrations reçoivent des données de configuration et peuvent modifier des gadgets logiciels, envoyer des invitations de salon et définir des rangs à votre place. + Taille maximum pour des téléversements sur ce serveur + Téléversements + Le fichier « %1$s » (%2$s) est trop gros pour être téléversé. La limite est %3$s. + Annuler le téléversement + Annuler le téléversement\? + Le téléversement de l’image a échoué + Enregistrer dans les téléchargements\? + ${app_name} peut accéder à votre carnet d’adresses pour trouver d’autres utilisateurs de Matrix avec leur adresse courriel et leur numéro de téléphone. +\n +\nAutorisez-vous l’accès à vos contacts à cette fin\? + Annuler le téléchargement\? + Mot de passe oublié\? + Vous n’avez aucun jeu d\'autocollants activé pour le moment. +\n +\nVoulez-vous en ajouter\? + Voulez-vous vraiment engager un nouvel appel vidéo\? + Voulez-vous vraiment engager un nouvel appel audio\? + Voulez-vous vraiment engager une nouvelle discussion avec %s\? + L’application s’est arrêtée anormalement la dernière fois. Souhaitez-vous ouvrir l’écran de rapport d’anomalie\? + Vous semblez secouer le téléphone avec agacement. Souhaitez-vous ouvrir soumettre un rapport d’anomalie\? + Veuillez décrire l’erreur. Qu’avez-vous fait\? Quel était le comportement attendu\? Que s’est-il réellement passé\? + Voulez-vous vraiment vous déconnecter\? + Êtes-vous sûr·e\? + Une téléconférence est déjà en cours! + 🎉 Tous les serveurs sont interdits de participation! Ce salon ne peut plus être utilisé. + Supprimer l’adresse « %1$s »\? + Dépublier l’adresse « %1$s »\? + Confirmer le NIP pour le désactiver + Changer votre NIP actuel + Changer le NIP + Si vous voulez réinitialiser votre code, appuyez sur NIP oublié pour vous déconnecter et le réinitialiser. + Protéger l’accès en utilisant un NIP et des données biométriques. + Nouveau NIP + Réinitialiser le NIP + NIP oublié\? + Entrez votre NIP + Impossible de valider le NIP, veillez en composer un nouveau. + Confirmez le NIP + Choisissez un NIP par sécurité + + 1 appel en cours (%1$s) ⋅ 1 appel en attente + 1 appel en cours (%1$s) ⋅ %2$d appels en attente + + + Appel en attente + %1$d appels en attente + + + %d entrée + %d entrées + + + Mauvais code, %d tentative restante + Mauvais code, %d tentatives restantes + + + Invitations envoyées à %1$s et un autre + Invitations envoyées à %1$s et %2$d autres + + + Afficher le périphérique avec lequel vous pouvez vérifier + Afficher les %d périphériques avec lesquels vous pouvez vérifier + + + Envoyer l’image en taille originale + Envoyer les images en taille originale + + + %d vote − Résultats finaux + %d votes − Résultats finaux + + + %d vote + %d votes + + + %d session active + %d sessions actives + + + Une personne + %1$d personnes + + + Trop de requêtes ont été envoyées. Vous pouvez réessayer dans %1$d seconde… + Trop de requêtes ont été envoyées. Vous pouvez réessayer dans %1$d secondes… + + + %d utilisateur a lu + %d utilisateurs ont lu + + + %1$s, %2$s et %3$d autre ont lu + %1$s, %2$s et %3$d autres ont lu + + + Sauvegarde d’%d clé… + Sauvegarde de %d clés… + + + %d nouvelle clé a été ajoutée à cette session. + %d nouvelles clés ont été ajoutées à cette session. + + + Sauvegarde restaurée avec %d clé. + Sauvegarde restaurée avec %d clés. + + + %d salon + %d salons + + + %d participant + %d participants + + + %d gadget logiciel actif + %d gadgets logiciels actifs + + + %d notification + %d notifications + + + %1$s : %2$d message + %1$s : %2$d messages + + + %d invitation + %d invitations + + + %d salon + %d salons + + + %d message notifié non lu + %d messages notifiés non lu + + + %d message notifié non lu + %d messages notifiés non lu + + + %1$d/%2$d clé importée avec succès. + %1$d/%2$d clés importées avec succès. + + + %d utilisateur banni + %d utilisateurs bannis + + + %d seconde + %d secondes + + + %1$s salon trouvé pour %2$s + %1$s salons trouvés pour %2$s + + + %d salon + %d salons + + Contenu de l’évènement + Contenu d’évènement + Envoyer des évènements d’état personnalisés + Envoyer un évènement d’état + Envoyer un évènement personnalisé + Envoyer des évènements m.room.server_acl + Inclure les évènement d’invitation/ajout/départ/expulsion/exclusion ainsi que les changements d’avatar et de nom d’affichage. + Afficher les évènement des membres du salon + Informations sur l’évènement + Afficher les évènements liés au compte + Informations sur le salon + + %d sélectionné + %d sélectionnés + + + %d nouveau message + %d nouveaux messages + + + %d j. + %d j. + + + %d h + %d h + + + %d min + %d min + + + %d s + %d s + + + %d membre + %d membres + + Voulez-vous vraiment quitter le salon\? + + %d membre actif + %d membres actifs + + + %d changement de statut + %d changements de statut + + + %d utilisateur + %d utilisateurs + + + Vous avez supprimé l’adresse alternative %1$s de ce salon. + Vous avez supprimé les adresses alternatives %1$s de ce salon. + + + %1$s a supprimé l’adresse alternative %2$s de ce salon. + %1$s a supprimé les adresses alternatives %2$s de ce salon. + + + Vous avez ajouté %1$s comme adresse alternative pour ce salon. + Vous avez ajouté %1$s comme adresses alternatives pour ce salon. + + + %1$s a ajouté %2$s comme adresse alternative pour ce salon. + %1$s a ajouté %2$s comme adresses alternatives pour ce salon. + + Autorisations + L’application ne peut pas créer de compte sur ce serveur d’accueil. +\n +\nVoulez-vous vous inscrire en utilisant un client web\? + L’application ne peut pas s’authentifier sur ce serveur d’accueil. Le serveur d’accueil prend en charge le(s) type(s) d’authentification suivant(s) : %1$s. +\n +\nVoulez-vous vous connecter en utilisant un client web\? + Son utilisation peut entraîner l’utilisation de fichiers témoins et le partage de données avec %s : + Courriels et numéros de téléphone + Courriel (facultatif) + Courriel + Courriel + Courriel + Courriel ou identifiant Matrix + Le domaine de votre adresse courriel n’est pas autorisé à s’inscrire sur ce serveur + Les options de découverte apparaîtront quand vous aurez ajouté un courriel. + Votre compte sera inutilisable de façon permanente. Vous ne pourrez plus vous connecter et personne ne pourra se réenregistrer avec le même identifiant d’utilisateur. Le compte quittera tous les salons auxquels il participe et tous ses détails seront supprimés du serveur d’identité. Cette action est irréversible. +\n +\nDésactiver votre compte ne nous fait pas oublier les messages que vous avez envoyés par défaut. Si vous souhaitez que nous oubliions vos messages, cochez la case ci-dessous. +\n +\nLa visibilité des messages dans Matrix est identique à celle des courriels. Si nous oublions vos messages, cela signifie que les messages que vous avez envoyés ne seront plus partagés avec les nouveaux utilisateurs ou les utilisateurs non enregistrés, mais les utilisateurs enregistrés qui ont déjà accès à ces messages en conserveront leur copie. + Cette invitation a été envoyée à %s, qui n’est pas associé à ce compte. +\nVous pouvez vous identifier avec un compte différent ou ajouter cette adresse courriel à votre compte. + Impossible de vérifier l’adresse courriel : assurez-vous d’avoir cliqué sur le lien dans l’courriel + Pour réinitialiser votre mot de passe, saisissez l’adresse courriel associée à votre compte : + J’ai vérifié mon adresse courriel + Aucune adresse courriel n’a été ajoutée à votre compte + Adresse courriel (facultatif) + Adresse courriel + Cette adresse courriel est déjà utilisée. + Adresse courriel manquante + Adresse courriel ou numéro de téléphone manquant + Veuillez vérifier votre courriel pour continuer votre inscription + L’inscription avec courriel et numéro de téléphone à la fois n’est pas prise en charge tant que l’API n’existe pas. Seul le numéro de téléphone sera pris en compte. +\n +\nVous pouvez ajouter votre adresse courriel à votre profil dans les paramètres. + L’adresse courriel liée à votre compte doit être saisie. + Un courriel a été envoyé à %s. Une fois que vous aurez suivi le lien qu’il contient, cliquez ci-dessous. + Impossible de s’inscrire : erreur d’appartenance de l’adresse courriel + Vous n’avez pas accès à ce message + Définir l’avatar + Vous avez bien changé les paramètres du salon + Sujet + Nom du salon + Stockez votre clé de sécurité en lieu sûr comme un gestionnaire de mots de passe ou un coffre-fort. + Enregistrer votre clé de sécurité + Entrez de nouveau votre phrase de sécurité pour la confirmer. + Phrase de sécurité + Entrez une phrase de sécurité que seul vous connaissez, celle-ci est utilisée pour sécuriser les mots de passe sur le serveur. + Définir une phrase de sécurité + Stockez votre clé de sécurité en lieu sûr comme un gestionnaire de mots de passe ou un coffre-fort. + Enregistrer votre clé de sécurité + Entrez une phrase secrète que seul vous connaissez, et générez une clé de sauvegarde. + Utiliser une phrase de sécurité + Générez une clé de sécurité à stocker en lieu sûr comme un gestionnaire de mots de passe ou un coffre-fort. + Utiliser une clé de sécurité + Configuration + Protection contre la perte d’accès aux messages et données chiffrées en sauvegardant les clés de chiffrement sur votre serveur. + Sauvegarde sécurisée + Activer la sauvegarde sécurisée + Démarrer la caméra + Arrêter la caméra + Activer le microphone + Désactiver le microphone + Ouvrir la discussion + Rôle + Définir le rôle + Valider + Renseignez l’URL d’un serveur d’identité + Sinon, vous pouvez renseigner n’importe quelle autre URL de serveur d’identité + Utiliser %1$s + Votre serveur d’accueil (%1$s) propose d’utiliser %2$s comme serveur d’identité + Le consentement de l\'utilisateur n’a pas été fourni. + Il n’y a actuellement aucune association avec cet identifiant. + L’association a échoué. + Veuillez d’abord accepter les termes du serveur d’identité dans les paramètres. + Veuillez d’abord configurer un serveur d’identité. + Cette opération n’est pas possible. Le serveur d’accueil est obsolète. + Ce serveur d’identité est obsolète. ${app_name} ne prend en charge que l’API V2. + Se déconnecter du serveur d’identité %s \? + Ouvrir les termes de %s + Chargement des langues disponibles… + Autres langues disponibles + Langue actuelle + Partagez ce code avec des gens pour qu’ils puissent le scanner pour vous ajouter et commencer à discuter. + Mon code + Partager mon code + Scanner un code QR + Nous n’avons pas pu inviter les utilisateurs. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez. + Ce n’est pas un code QR matrix valide + Invitations envoyées à %1$s et %2$s + Invitation envoyée à %1$s + 🔐️ Rejoins-moi sur ${app_name} + Salut, parle-moi sur ${app_name} : %s + Ajouter des amis + Inviter des utilisateurs + Invitation des utilisateurs… + INVITER + Ajouter des personnes + Ajouter des membres + Nous n’avons pas pu créer votre conversation privée. Vérifiez les utilisateurs que vous souhaitez inviter et réessayez. + Le lien %1$s vous emmène sur un autre site : %2$s. +\n +\nVoulez-vous vraiment poursuivre \? + Vérifiez ce lien + Veuillez choisir un mot de passe. + Veuillez choisir un nom d’utilisateur. + Échec de la mise en place de la signature croisée + Marquer comme fiable + Confirmez votre identité en vérifiant cet identifiant, lui donnant ainsi accès aux messages chiffrés. + Confirmez votre identité en vérifiant cette connexion depuis une de vos autres sessions, ce qui lui permettra d’avoir accès à vos messages chiffrés. + Vérifier de façon interactive avec des émojis + Vérifier la connexion + Vérifier manuellement avec un texte + Vérifiez la nouvelle connexion accédant à votre compte : %1$s + Vérifiez toutes les sessions pour vous assurer que votre compte et vos messages sont en sécurité + Vérifiez où vous vous êtes connecté + Chiffré par un appareil non vérifié + Non chiffré + envoie de la neige ❄️ + envoie des confettis 🎉 + Envoie le message avec de la neige + Envoie le message avec des confettis + Vous redémarrerez sans aucun historique, message, appareil ou utilisateurs connus + Si vous réinitialisez tout + Faites uniquement ceci si vous n\'avez aucun autre appareil pouvant vérifier celui-ci. + Réinitialiser tout + Perdu ou oublié toutes les options de récupération \? Réinitialisez tout + Échec d’accès au coffre secret + La sauvegarde n’a pas pu être déchiffrée avec cette clé de récupération : veuillez vérifier que vous avez saisi la bonne clé de récupération. + Sélectionnez votre clé de récupération ou saisissez-la manuellement avec le clavier ou en la copiant depuis le presse-papiers + Utiliser la clé de récupération + Utilisez votre %1$s ou votre %2$s pour continuer. + Seulement pris en charge dans les salons chiffrés + Force la session de groupe sortante actuelle dans un salon chiffré à être abandonnée + Utilisez la dernière version de ${app_name} sur vos autres appareils : + ou un autre client Matrix qui prend en charge la signature croisée + ${app_name} iOS +\n${app_name} Android + ${app_name} Web +\n${app_name} pour Bureau + Utilisez la dernière version de ${app_name} sur vos autres appareils : ${app_name} Web, ${app_name} pour Bureau, ${app_name} iOS, ${app_name} pour Android, ou un autre client Matrix qui prend en charge la signature croisée + Définir un nouveau mot de passe de compte… + Impossible d’enregistrer le fichier multimédia + Impossible d’ajouter le fichier multimédia à la galerie + Fichier multimédia ajouté à la galerie + L’activation de ce paramètre ajoute FLAG_SECURE à toutes les activités. Redémarrez l’application pour que la modification soit prise en compte. + Empêcher les captures d’écran de l’application + Clé de récupération de sauvegarde de clés + Vous ne connaissez pas votre phrase secrète de sauvegarde de clés, vous pouvez %s. + utiliser votre clé de récupération de sauvegarde de clés + Saisissez votre phrase secrète de sauvegarde de clés pour continuer. + %1$s (%2$s) + Stockage du secret de sauvegarde dans le SSSS + Génération de la clé SSSS à partir de la clé de récupération + Génération de la clé SSSS à partir de la phrase secrète (%s) + Génération de la clé SSSS à partir de la phrase secrète + Récupération de la clé de courbe + Vérification de la clé de sauvegarde (%s) + Vérification de la clé de sauvegarde + Veuillez entrer une clé de récupération + Ce n’est pas une clé de récupération valide + Phrase de récupération + Entrez %s + Utiliser un fichier + Entrez votre %s pour continuer + Vérifiez vos sessions et les autres pour garantir la sûreté de vos discussions + Activer la signature croisée + Mise à niveau du chiffrement disponible + Message… + Ce compte a été désactivé. + Nom et/ou mot de passe incorrect(s). Le mot de passe saisi commence ou se termine par des espaces, veuillez le vérifier. + Envoie un message en texte simple, sans l’interpréter comme du Markdown + Définir l’importance des notifications par évènement + Résolution de problèmes + Quand les salons sont mis à niveau + Messages chiffrés dans les conversations de groupe + Messages chiffrés dans les conversations individuelles + Messages contenant @room + Configuration des notifications + Échec de l’importation des clés + En attente de %s… + On y est presque ! En attente de la confirmation… + On y est presque ! L’autre appareil affiche-t-il le même bouclier \? + "Sujet : " + Ajouter un sujet + %s pour permettre aux gens de connaître le sujet de ce salon. + Ceci est le début de l’historique de votre conversation privée avec %s. + Ceci est le début de cette conversation. + Ceci est le début de %s. + Vous avez rejoint la conversation. + %s a rejoint la conversation. + Vous avez créé et configuré ce salon. + %s a créé et configuré ce salon. + Le chiffrement utilisé par ce salon n’est pas pris en charge + Chiffrement désactivé + Les messages de ce salon sont chiffrés de bout en bout. + Les messages de ce salon sont chiffrés de bout en bout. Apprenez-en plus et vérifiez les utilisateurs sur leur profil. + Chiffrement activé + Si vous annulez maintenant, vous pourrez perdre les messages et données chiffrés si vous perdez accès à vos identifiants. +\n +\nVous pouvez aussi activer la sauvegarde sécurisée et gérer vos clés dans les paramètres. + La configuration d’une phrase de récupération vous permet des sécuriser et déverrouiller les messages chiffrés et les vérifications. + La configuration d’un mot de passe de messages vous permet des sécuriser et déverrouiller les messages chiffrés et les vérifications. +\n +\nSi vous ne voulez pas définir un mot de passe de messages, générez plutôt une clé de messages. + Vous ne pouvez pas faire ça depuis votre portable + Copiez-le sur votre stockage dans le cloud personnel + Sauvegardez-le sur une clé USB ou un disque de sauvegarde + Imprimez-le et conservez-le en lieu sûr + Votre %2$s et votre %1$s sont bien réglés. +\n +\nConservez-les en lieu sûr ! Vous en aurez besoin pour accéder aux messages chiffrés et à vos informations sécurisées si vous perdez toutes vos sessions actives. + Configuration de la sauvegarde de clés + Synchronisation de la clé d’auto-signature + Synchronisation de la clé de l’utilisateur + Synchronisation de la clé maîtresse + Définition de la clé par défaut du SSSS + Génération d’une clé sécurisée depuis la phrase secrète + Publication des clés d’identité créées + Utilisez cette %1$s comme filet de sécurité au cas où vous oublieriez votre %2$s. + Terminer + Conservez-le en lieu sûr + Vous avez terminé ! + Votre clé de récupération + Configuration de la récupération. + Cela peut prendre plusieurs secondes, veuillez patienter. + Entrez une phrase de sécurité que seul vous connaissez, celle-ci est utilisée pour sécuriser les mots de passe sur le serveur. + Ne réutilisez pas votre mot de passe de compte. + Saisissez à nouveau votre %s pour le confirmer. + Sécurisez et débloquez vos messages chiffrés et vos vérifications avec un %s. + Saisissez votre %s pour continuer. + Confirmez le %s + Générez une clé de messages + Définissez un %s + Mot de passe du compte + clé des messages + Phrase de récupération + Non-concordance d’utilisateur + Non-concordance de clé + Un message non valide a été reçu + La session a reçu un message inattendu + Le SAS ne correspond pas + L’engagement de hachage ne correspond pas + La session ne peut pas s’accorder sur une méthode de concordance, de hachage, de MAC ou de SAS de clé + La session n’est pas au courant de cette transaction + Le processus de vérification a expiré + L’utilisateur a annulé la vérification + %s veut vérifier votre session + Demande de vérification + Vérification de session interactive + La vérification est annulée. +\nMotif : %s + Votre interlocuteur a annulé la vérification. +\n%s + Demande annulée + Vérification de clé + Utiliser la vérification traditionnelle. + Rien n’apparaît \? Tous les clients ne prennent pas encore en charge la vérification interactive. Utilisez la vérification traditionnelle. + Compris + Les messages sécurisés avec cet utilisateur sont chiffrés de bout en bout et ne peuvent être lus par des tiers. + Vous avez bien vérifié cette session. + Vérifié ! + Attente de la confirmation du partenaire… + Voir la demande + Vous avez reçu une demande de vérification entrante. + Nouvelle invitation + Nouveaux messages + Salon + Nouvel évènement + %1$s et %2$s + %1$s dans %2$s et %3$s + %1$s dans %2$s + Saisir du texte ici… + Tous les salons natifs sur %s + Tous les salons sur le serveur %s + URL du serveur d’accueil + Saisir un serveur d’accueil pour lister ses salons publics + Le serveur est peut-être indisponible ou surchargé + Sélectionner un répertoire de salons + Ce salon contient des sessions inconnues, qui n’ont pas été vérifiées. +\nCela signifie qu’il n’y a aucune garantie que les sessions appartiennent aux utilisateurs qu’elles prétendent. +\nNous vous recommandons d’effectuer le processus de vérification pour chaque session avant de continuer, mais vous pouvez renvoyer le message sans vérifier si vous préférez. +\n +\nSessions inconnues : + Le salon contient des sessions inconnues + Je confirme que les clés correspondent + Si elles ne correspondent pas, la sécurité de votre communication est peut-être compromise. + Confirmez en comparant les informations suivantes avec les paramètres utilisateur dans votre autre session : + Vérifier la session + Supprimer de la liste noire + Ajouter à la liste noire + Annuler la vérification + Vérifier + aucun + adresse IP inconnue + session inconnue + Sur liste noire + Vérifié + Non vérifié + Ne jamais envoyer de messages chiffrés aux sessions non vérifiées depuis cette session. + Chiffrer uniquement vers les sessions vérifiées + Importer + Importer les clés à partir d’un fichier local + Importer les clés des salons + Importer les clés de chiffrement des salons + Gérer la sauvegarde de clés + Récupération des messages chiffrés + Les clés ont bien été exportées + Les clés de chiffrement du salon ont été sauvegardées dans « %s ». +\n +\nAttention : ce fichier peut être supprimé si l’application est désinstallée. + Veuillez créer une phrase secrète pour chiffrer les clés exportées. Vous devrez saisir cette même phrase secrète afin de pouvoir importer les clés. + Exporter + Exporter les clés vers un fichier local + Exporter les clés des salons + Exporter les clés E2E des salons + Empreinte Ed25519 + Vérification + Clé de la session + Identifiant de session + Nom public + Le nom public d’une session est visible par les personnes avec qui vous communiquez + Nom public (visible par les personnes avec qui vous communiquez) + Nom public + Informations sur la session de l’expéditeur + Erreur de déchiffrement + Identifiant de session + Algorithme + Clé d’empreinte Ed25519 déclarée + Clé d’identité Curve25519 + Identifiant utilisateur + Informations sur le chiffrement de bout en bout + %s a essayé de charger un point précis dans l’historique du salon mais ne l’a pas trouvé. + Thème + Répertoire + Activer le chiffrement +\n(attention : ne peut plus être désactivé par la suite !) + Le chiffrement est désactivé sur ce salon. + Le chiffrement est activé sur ce salon. + Copier l’adresse du salon + Copier l’identifiant du salon + Désactiver comme adresse principale + Définir comme adresse principale + Avertissements concernant l’adresse principale + Vous n’aurez aucune adresse principale spécifiée pour ce salon. + « %s » n’est pas un format valide pour un alias + Format d’alias invalide + « %s » n’est pas un identifiant de communauté valide + Identifiant de communauté invalide + Nouvel identifiant de communauté (ex. +foo:matrix.org) + Ce salon n’a de badge pour aucune communauté + Nouvelle adresse (par exemple #foo:matrix.org) + Ce salon n’a pas d’adresse locale + Ne jamais envoyer de message chiffré aux sessions non vérifiées sur ce salon, depuis cette session. + Chiffrer uniquement pour les sessions vérifiées + Vous devez vous déconnecter pour pouvoir activer le chiffrement. + Le chiffrement de bout en bout est actif + Chiffrement de bout en bout + Ce sont des fonctionnalités expérimentales qui peuvent se comporter de façon inattendue. À utiliser avec précaution. + Expérimental + Adresses + Version du salon + L’identifiant interne de ce salon + Avancé + Utilisateurs bannis + Tous ceux qui connaissent le lien du salon, y compris les visiteurs + Tous ceux qui connaissent le lien du salon, à part les visiteurs + Seules les personnes qui ont été invitées + Pour faire référence à un salon, il doit avoir une adresse. + Uniquement les membres (depuis qu’ils sont arrivés) + Uniquement les membres (depuis leur invitation) + Uniquement les membres (à partir de l’activation de cette option) + N’importe qui + Impossible de récupérer la visibilité du répertoire de salons actuel (%1$s). + Publier ce salon dans le répertoire public de %1$s \? + Dé-publier cette adresse + Publier cette adresse + Ajouter une adresse locale + Ce salon n’a pas d’adresse locale + Définissez des adresses pour ce salon afin que les utilisateurs puissent le trouver via votre serveur d’accueil(%1$s) + Adresse locale + Nouvelle adresse publiée (par ex. #alias:serveur) + Aucune adresse publiée pour le moment. + Aucune adresse publiée pour le moment, ajoutez en une ci-dessous. + Publier ce salon dans le répertoire public de %1$s \? + Publier + Publier une nouvelle adresse manuellement + Autres adresses publiées : + Adresse principale + Ceci est l’adresse principale + Les adresses publiées peuvent être utilisées par n’importe qui pour rejoindre votre salon. Pour pouvoir publier une adresse, elle doit d’abord être définie comme adresse locale. + Adresses publiées + Adresses du salon + Voir et gérer les adresses de ce salon, et sa visibilité dans le répertoire des salons. + Adresses du salon + Accès au salon + Qui peut accéder à ce salon \? + Les modifications de visibilité de l’historique ne s’appliqueront qu’aux messages ultérieurs dans ce salon. La visibilité de l’historique actuel demeurera inchangée. + Qui peut lire l’historique \? + Accès à l’historique du salon + Accès au salon + Notifications + Lister ce salon dans le répertoire des salons + Accès et visibilité + Aucune + Priorité basse + Favori + Étiqueté comme : + Étiquette du salon + Sujet + Nom du salon + Photo du salon + Pas de limite + 1 mois + 1 semaine + 3 jours + Vous n’êtes pour le moment membre d’aucune communauté. + Badge + Jouer le son de l’obturateur + Choisir + Source de médias par défaut + Choisir + Compression par défaut + Média + Informations additionnelles : %s + Une erreur est survenue lors de la vérification de votre numéro de téléphone. + Code + Erreur lors de la validation de votre numéro de téléphone + Saisir un code d’activation + Nous avons envoyé un SMS avec un code d’activation. Veuillez saisir ce code ci-dessous. + Vérification du téléphone + Numéro de téléphone non valide pour le pays sélectionné + Numéro de téléphone + Choisissez un pays + Pays + Choisissez un pays + Voulez-vous vraiment supprimer le %1$s %2$s \? + Voulez-vous vraiment supprimer cette cible de notification \? + Les mots de passe ne correspondent pas + Afficher tous les messages de %s \? +\n +\nVeuillez noter que cette action redémarrera l’application et pourra prendre un certain temps. + Votre mot de passe a été mis à jour + Le mot de passe n’est pas valide + Échec de mise à jour du mot de passe + Mettre à jour le mot de passe + Confirmer le nouveau mot de passe + Nouveau mot de passe + Mot de passe actuel + Changer le mot de passe + Mot de passe + Ce numéro de téléphone est déjà utilisé. + Vérification en attente + Choisissez une langue + Le code est requis à l’ouverture d’${app_name}. + Le code est demandé après 2 minutes d\'inutilisation d’${app_name}. + Demander le code après 2 minutes + Afficher uniquement le numéro de messages non-lus dans une simple notification. + Afficher les détails comme les noms des salons et le contenu du message. + Afficher le contenu dans les notifications + Le code est la seule façon de déverrouiller ${app_name}. + Activer les données biométriques comme les empreintes digitales ou la reconnaissance faciale. + Activer les données biométriques + Activer le code + Configurer la protection + Protéger l’accès + Pour réinitialiser votre code, vous devez vous reconnecter et en créer un nouveau. + Trop d’erreurs, vous avez été déconnecté + Attention ! Dernière tentative avant déconnexion ! + Vérifiez vos paramètres pour activer les notifications push + Les notifications push sont désactivées + Échec de la révocation du bannissement de l’utilisateur + Banni par %1$s + Révoquer l’invitation à %1$s \? + Révoquer l’invitation + Rechercher des contacts sur Matrix + Carnet d’adresses + Votre carnet d’adresses est vide + Récupération de vos contacts… + Rechercher dans mes contacts + Carnet d’adresse + Votre carnet d’adresses est vide + Ajouter depuis mon carnet d’adresses + Enregistrer la clé de récupération + EN SAVOIR PLUS + COMPRIS + Nous sommes heureux de vous annoncer que nous avons un nouveau nom ! Votre application est à jour et vous êtes connectés à votre compte. + Riot est désormais Element ! + Attente de l’historique du chiffrement + Impossible d’accéder à ce message car l’envoyeur n’a intentionnellement pas envoyé les clés + Vous ne pouvez pas accéder à ce message car l’envoyeur n’a pas confiance en votre session + Impossible d’accéder à ce message car vous avez été bloqué par l’envoyeur + À cause du chiffrement de bout en bout, vous pouvez avoir besoin d’attendre l’arrivée du message de quelqu’un car les clés de chiffrement ne vous ont pas été correctement envoyées. + Impossible de déchiffrer + Attente du message, cela peut prendre du temps + Identifiant de l’utilisateur + Si vous avez créé un compte sur un serveur d\'accueil, utilisez ci-dessous votre identifiant Matrix (par ex. : @utilisateur:domaine.com) et mot de passe. + Se connecter + Me connecter avec mon identifiant Matrix + Sinon, si vous avez déjà un compte et que vous connaissez votre identifiant Matrix et votre mot de passe, vous pouvez utiliser cette méthode : + Ce serveur d\'accueil utilise une version obsolète. Demandez à l’administrateur de votre serveur d\'accueil de le mettre à jour. Vous pouvez continuer, mais certaines fonctionnalités peuvent ne pas fonctionner correctement. + Ce serveur d’accueil utilise une version trop ancienne pour s’y connecter. Demandez à l’administrateur de votre serveur d’accueil de le mettre à jour. + Serveur d’accueil obsolète + Le code saisi n’est pas correct. Veuillez vérifier. + Acceptez les termes pour continuer + Veuillez compléter le captcha + Sélectionner un serveur d’accueil personnalisé + Sélectionner Element Matrix Services + Sélectionner matrix.org + Votre compte n’est pas encore crée. +\n +\nArrêter le processus de création \? + Attention + Ce nom d’utilisateur est déjà pris + Suivant + Mot de passe + Nom d’utilisateur + S’inscrire sur %1$s + Le numéro de téléphone n’a pas l’air d’être valide. Veuillez le vérifier + Les numéros de téléphone internationaux doivent commencer par « + » + Veuillez utiliser le format international (le numéro de téléphone doit commencer par « + ») + Suivant + Renvoyer + Saisir le code + Nous avons envoyé un code à %1$s. Saisissez-le ci-dessous pour vérifier que c’est bien vous. + Confirmer le numéro de téléphone + Suivant + Numéro de téléphone (facultatif) + Numéro de téléphone + Veuillez utiliser le format international. + Définir un numéro de téléphone pour autoriser éventuellement des personnes à vous découvrir. + Définir le numéro de téléphone + Suivant + Votre mot de passe n’a pas encore été changé. +\n +\nArrêter le processus de changement \? + Attention + Retourner à l’authentification + Vous avez été déconnecté de toutes les sessions et ne recevrez plus de notification. Pour réactiver les notifications, reconnectez-vous sur chaque appareil. + Votre mot de passe a été réinitialisé. + Succès ! + Touchez le lien pour confirmer votre nouveau mot de passe. Après avoir suivi le lien qu’il contient, cliquez ci-dessous. + Vérifiez votre boîte de réception + Poursuivre + Le changement de mot de passe réinitialisera toutes les clés de chiffrement sur toutes vos sessions, rendant l’historique des discussions chiffrées illisible. Configurez la sauvegarde de clés ou exportez vos clés de salon depuis une autre session avant de réinitialiser votre mot de passe. + Attention ! + Nouveau mot de passe + Suivant + Réinitialiser le mot de passe sur %1$s + Désolé, ce serveur n’accepte pas de nouveau compte. + Une erreur est survenue pendant le chargement de la page : %1$s (%2$d) + Entrez l’adresse du serveur que vous voulez utiliser + Saisir l’adresse de Element ou du serveur de Modular que vous voulez utiliser + Hébergement privé pour les organisations + Adresse + Adresse Element Matrix Services + Effacer l’historique + Continuer avec l’authentification unique + S’authentifier + S’inscrire + S’authentifier sur %1$s + Se connecter à un serveur personnalisé + Se connecter à Element Matrix Services + Se connecter à %1$s + Poursuivre + authentification unique + Se connecter avec %s + S’inscrire avec %s + Poursuivre avec %s + Ou + Paramètres personnalisés et avancés + Autre + En savoir plus + Hébergement premium pour les organisations + Rejoignez des millions de personnes gratuitement sur le plus grand serveur public + Sélectionner un serveur + Démarrer + Étendez et personnalisez votre expérience + Gardez vos conversations privées avec le chiffrement + Discutez directement avec des personnes ou avec des groupes + Libérez votre communication. + Messages non lus + Vous avez rendu ceci uniquement accessible par invitation. + %1$s a rendu ceci uniquement accessible par invitation. + Vous avez rendu le salon uniquement accessible par invitation. + %1$s a rendu le salon accessible uniquement par invitation. + Vous avez rendu ce salon public à tous ceux qui en connaissent le lien. + %1$s a rendu le salon public à tous ceux qui en connaissent le lien. + Clic long sur un salon pour voir plus d’options + Vous n’ignorez aucun utilisateur + Saisir des mots-clés pour trouver une réaction. + Spoiler + Envoie le message fourni comme un spoiler + Vous n’avez rien changé + %1$s n’a effectué aucun changement + Paramètres du salon + Quitter le salon + Retirer de la priorité faible + Ajouter à la priorité faible + Supprimer des favoris + Ajouter aux favoris + Paramètres + Sourdine + Seulement les mentions + Tous les messages + Tous les messages (sonore) + Bloquer l’utilisateur + Il n’y a aucune connexion au réseau pour le moment + ${app_name} a besoin de votre permission pour sauvegarder vos clés de chiffrement sur le disque. +\n +\nAutorisez l’accès dans le prochaine fenêtre pour pouvoir exporter vos clés manuellement. + Ce contenu a été signalé comme inapproprié. +\n +\nSi vous ne voulez plus voir de contenu de cet utilisateur, vous pouvez l’ignorer pour masquer ses messages. + Signalé comme inapproprié + Ce contenu a été signalé comme spam. +\n +\nSi vous ne voulez plus voir de contenu de cet utilisateur, vous pouvez l’ignorer pour masquer ses messages. + Signalé comme spam + Ce contenu a été signalé. +\n +\nSi vous ne voulez plus voir de contenu de cet utilisateur, vous pouvez l’ignorer pour masquer ses messages. + Contenu signalé + BLOQUER L’UTILISATEUR + SIGNALER + Motif de signalement de ce contenu + Signaler ce contenu + Signalement personnalisé… + C’est inapproprié + C’est du spam + Il n’y a aucun fichier dans ce salon + Langue + Interface utilisateur + Activer « Autoriser les intégrations » dans les paramètres pour faire ceci. + Les intégrations sont désactivées + Gestionnaire d’intégrations + Autoriser les intégrations + Serveur d’identité + Serveur d’accueil + Connecté en tant que + Valider + Mot de passe : + Authentification + Cette opération nécessite de s’authentifier à nouveau. +\nPour continuer, veuillez saisir votre mot de passe. + %1$s @ %2$s + Vu la dernière fois + Mettre à jour le nom public + Nom public + Identifiant + Informations de la session + Le mode d’économie de données utilise un filtre spécifique qui ignore les notifications de présence et de saisie. + Mode économie de données + Oui, je veux aider ! + Veuillez autoriser la collecte des données pour nous aider à améliorer ${app_name}. + ${app_name} collecte des données statistiques anonymes pour nous permettre d’améliorer l’application. + Envoyer des statistiques d’utilisation + Statistiques d’utilisation + L’application a besoin de la permission de fonctionner en arrière-plan + Confidentialité réduite + Normal + Ignorer l’optimisation + Si un utilisateur laisse un appareil débranché et immobile pour une longue durée, avec l’écran éteint, l’appareil entre en mode veille.. Cela empêche les applications d’accéder au réseau et reporte leurs tâches, synchronisations et alarmes standard. + ${app_name} n’est pas affecté par l’optimisation de la batterie. + Optimisation de la batterie + Désactiver les restrictions + Les restrictions en arrière-plan sont activées pour ${app_name}. +\nLes tâches que l’application essaiera d’effectuer seront fortement restreintes tant qu’elle sera en arrière-plan et cela pourra affecter les notifications. +\n%1$s + Les restrictions en arrière-plan sont désactivées pour ${app_name}. Ce test devrait être lancé en utilisant les données mobiles (pas le Wi-Fi). +\n%1$s + Vérifier les restrictions en arrière-plan + Activer le démarrage au démarrage de l’appareil + Le service ne démarrera pas quand l’appareil sera redémarré, vous ne recevrez pas de notifications tant que ${app_name} n’aura pas été lancé au moins une fois. + Le service démarrera quand l’appareil sera redémarré. + Lancer au démarrage + Le redémarrage du service a échoué + Le service a été tué et redémarré automatiquement. + Redémarrage automatique du service de notifications + Démarrer le service + Le service de notifications n’est pas lancé. +\nEssayez de redémarrer l’application. + Le service de notifications est lancé. + Service de notifications + La notification a été cliquée ! + Veuillez cliquer sur la notification. Si vous ne voyez pas la notification, veuillez vérifier les paramètres système. + Affichage de la notification + Vous voyez la notification ! Cliquez-moi dessus ! + Erreur de réception du push. La solution pourrait être de réinstaller l’application. + L’application reçoit le PUSH + L’application attend le PUSH + Tester le Push + Le jeton FCM n’a pas pu être enregistré sur le serveur d’accueil : +\n%1$s + Le jeton FCM a été correctement enregistré sur le serveur d’accueil. + Enregistrement du jeton + Ajouter un compte + [%1$s] +\nCette erreur est indépendante de ${app_name}. Il n’y pas de compte Google sur l’appareil. Veuillez ouvrir le gestionnaire de comptes et ajouter un compte Google. + [%1$s] +\nCette erreur est indépendante de ${app_name}. Elle peut survenir pour plusieurs raisons. Cela peut fonctionner si vous réessayez plus tard. Vous pouvez aussi vérifier que le Service Google Play n’a pas un usage limité de données dans les paramètres système. Cela peut aussi arriver sur une ROM personnalisée. + [%1$s] +\nCette erreur est indépendante de ${app_name} et, selon Google, cette erreur indique que l’appareil a enregistré trop d’applications avec FCM. Cette erreur ne survient que s’il y a un nombre d’applications anormalement élevé, et ne devrait donc pas affecter un utilisateur normal. + Le jeton FCM n’a pas pu être récupéré : +\n%1$s + Le jeton FCM a été récupéré avec succès : +\n%1$s + Jeton Firebase + Réparer les services Google Play + ${app_name} utilise les services Google Play pour envoyer les notifications mais ils n’ont pas l’air d’être configurés correctement : +\n%1$s + L’APK des services Google Play est disponible et à jour. + Vérification des services Google Play + Vérifier les paramètres + Échec du chargement de vos règles personnalisées, veuillez réessayer. + Certaines notifications sont désactivées dans vos paramètres personnalisés. + Remarquez que certains messages sont réglés pour être silencieux (ils produiront une notification sans son). + Paramètres personnalisés. + Activer + Les notifications ne sont pas activées pour cette session. +\nVeuillez vérifier les paramètres de ${app_name}. + Les notifications sont activées pour cette session. + Paramètres de la session. + Activer + Les notifications sont désactivées pour votre compte. +\nVeuillez vérifier les paramètres du compte. + Les notifications sont activées pour votre compte. + Paramètres du compte. + Ouvrir les paramètres + Les notifications sont désactivées dans les paramètres systèmes. +\nVeuillez vérifier les paramètres système. + Les notifications sont activées dans les paramètres système. + Paramètres système. + Au moins un test a échoué, veuillez envoyer un rapport d’anomalie pour nous aider à résoudre le problème. + Au moins un test a échoué, essayez les solutions suggérées. + Le diagnostic de base est passé. Si vous ne recevez toujours pas de notifications, envoyez un rapport d’anomalie pour nous aider à résoudre le problème. + Exécution… (%1$d sur %2$d) + Lancer les tests + Diagnostics de résolution de problème + Résoudre les problèmes de notification + Confidentialité des notifications + Importance des notifications par évènement + Paramètres de notification avancés + Supprimer %s \? + Numéros de téléphone + Une authentification est nécessaire + Vous ne pouvez pas faire ceci depuis ${app_name} mobile + Confirmez votre mot de passe + Affiche les informations de l’application dans les paramètres système. + Informations sur l’application + Ajouter un numéro de téléphone + Aucun numéro de téléphone n’a été ajouté à votre compte + Téléphone + Nom affiché + Image de profil + Politique de confidentialité + Droits d’auteur + Licences tierces + Termes et conditions + Version %s + Version + Paramètres + Messages + Ajouter à l’écran d’accueil + Abandonner + Quitter la discussion + Conversation privée + Passer en faible priorité + Favori + Pas de notification + Notifier uniquement lorsque mon nom est mentionné + Notification pour chaque message + Notification sonore pour chaque message + Recherche dans le répertoire… + Parcourir le répertoire + Saisissez un identifiant ou un alias de salon + Rejoindre un salon + Rejoindre le salon + Créer un salon + Nouvelle discussion + INVITATIONS + PRIORITÉ BASSE + SALONS + FAVORIS + RÉPERTOIRE + REJOINDRE + La recherche dans les salons chiffrés n\'est pas encore prise en charge. + FICHIERS + PARTICIPANTS + MESSAGES + SALONS + Aucun résultat + Filtrer les utilisateurs exclus + Filtrer les membres du salon + Rechercher + Annuler le téléchargement + Voulez-vous cacher tous les messages de cet utilisateur \? +\n +\nVeuillez noter que cette action redémarrera l’application et pourra prendre un certain temps. + Motif du signalement de ce contenu + MEMBRES + INVITÉS + Paramètres + Fichiers + Participants + Changer le sujet + Mettre à niveau le salon + Changer les permissions + Changer le nom du salon + Changer la visibilité de l’historique + Activer le chiffrement du salon + Changer l’adresse principale du salon + Changer l’avatar du salon + Notifier tout le monde + Supprimer les messages des autres + Bannir des utilisateurs + Expulser des utilisateurs + Mettre à jour les paramètres + Inviter des utilisateurs + Envoyer des messages + Rôle par défaut + Vous n’avez pas la permission de changer les rôles requis pour changer différentes parties du salon + Indiquer les rôles requis pour changer les différentes parties du salon + Voir et changer les rôles requis pour changer les différentes parties du salon. + Permissions du salon + Acceptez le certificat uniquement si l’administrateur du serveur a publié une empreinte correspondant à celle ci-dessus. + Le certificat n’est plus celui qui a été approuvé par votre téléphone. Le serveur a peut-être renouvelé son certificat. Contactez l’administrateur du serveur pour lui demander l’empreinte de son certificat. + Le certificat n’est plus celui qui a été approuvé par votre téléphone. Ce comportement est INATTENDU. Il est recommandé de ne PAS ACCEPTER ce nouveau certificat. + Si l’administrateur du serveur a confirmé que cela était normal, assurez-vous que l’empreinte ci-dessous correspond à l’empreinte fournie par l’administrateur. + Cela pourrait signifier que quelqu’un intercepte malicieusement votre trafic ou que votre téléphone ne fait pas confiance au certificat fourni par le serveur distant. + Impossible de vérifier l’identité du serveur distant. + Empreinte (%s) : + Ignorer + Se déconnecter + Ne pas faire confiance + Faire confiance + Vous n’avez pas la permission de poster dans ce salon + Fichier non trouvé + Supprimer les messages non envoyés + Renvoyer les messages non envoyés + Tout annuler + Tout renvoyer + Messages non envoyés car des sessions inconnues sont présentes. %1$s ou %2$s maintenant \? + Messages non envoyés. %1$s ou %2$s maintenant \? + La connexion au serveur a été perdue. + Envoyer une réponse (non chiffrée)… + Envoyer une réponse chiffrée… + Envoyer un message (non chiffré)… + Envoyer un message chiffré… + %1$s, %2$s et d’autres écrivent… + %1$s et %2$s écrivent… + %s écrit… + Rechercher + Vous n’avez pas encore cliqué sur le lien dans l’courriel + Identifiant, nom ou adresse courriel + Entrez une ou plusieurs adresses courriel ou identifiants Matrix + Identifiant au mauvais format. Une adresse courriel ou un identifiant Matrix au format « @utilisateur:domaine » est attendu + Ajouter une adresse courriel + Adresses courriel + Assurez-vous d\'avoir cliqué sur le lien envoyé par courriel. + Vérifiez votre courriel et cliquez sur le lien qu’il contient. Une fois cela fait, cliquez sur continuer. + Impossible de vérifier l’adresse courriel. Vérifiez vos courriels et cliquez sur le lien qui a été envoyé. Ensuite, cliquez sur continuer. + Cette adresse courriel est déjà utilisée. + Cette adresse courriel n’a pas été trouvée. + Une erreur est survenue lors de la vérification de votre adresse courriel. + Gérer les courriels et numéros de téléphone liés à votre compte Matrix + Tout comme les courriels, les comptes ont un serveur d’accueil, même si vous pouvez parler à tout le monde + Adresses courriel découvrables + La déconnexion du serveur d’identité signifie que vous ne pourrez plus être découvrable par les autres utilisateurs et que vous ne pourrez plus inviter d’autres personnes par courriel ou par téléphone. + Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation + Nous vous avons envoyé un courriel de confirmation à %s, consultez vos courriels et cliquez sur le lien de confirmation + Envoyer des courriels et des numéros de téléphone + Vous avez donné votre autorisation pour envoyer des courriels et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts. + Vous n’avez pas donné votre autorisation pour envoyer des courriels et des numéros de téléphone à ce serveur d’identité pour découvrir d\'autres utilisateurs à partir de vos contacts. + Envoyer des courriels et des numéros de téléphone + Dans le but de découvrir des contacts que vous connaîtriez, acceptez-vous d\'envoyer vos données de contact (numéros de téléphone et/ou courriels) au serveur d’identité configuré (%1$s) \? +\n +\nPour une meilleure protection de la vie privée, les données seront condensées (hash) avant l’envoi. + Vous partagez actuellement des adresse courriels et des numéros de téléphone sur le serveur d’identité %1$s. Vous devrez vous reconnecter à %2$s pour arrêter de les partager. + Acceptez les conditions de service du serveur d’identité (%s) pour vous permettre d’être découvrable avec une adresse courriel ou un numéro de téléphone. + Un courriel de vérification sera envoyé à votre adresse pour confirmer la configuration de votre nouveau mot de passe. + Cet courriel n’est lié à aucun compte + Un courriel de vérification a été envoyé à %1$s. + J’ai vérifié mon adresse courriel + Définir l’adresse courriel + Définir une adresse courriel pour récupérer votre compte. Plus tard, vous pourrez éventuellement autoriser des personnes à vous retrouver avec votre adresse courriel. + Nom d’utilisateur ou courriel + Vérifiez vos courriels + Nous avons envoyé un courriel à %1$s. +\nCliquez sur le lien qu’il contient pour continuer la création du compte. + Pour votre vie privée, ${app_name} prend uniquement en charge l’envoi des adresses courriel et des numéros de téléphone hachés. + Cet courriel n’est associé à aucun compte. + ${app_name} peut accéder à votre carnet d’adresses pour trouver d’autres utilisateurs de Matrix avec leur numéro de téléphone et leur adresse courriel. Veuillez autoriser l’accès dans la prochaine fenêtre pour découvrir les utilisateurs du carnet d’adresses joignables via ${app_name}. + Renseignez une adresse courriel pour la récupération de compte. Utilisez ensuite un courriel ou un téléphone pour être éventuellement découvrable par les personnes qui vous connaissent. + Ceci ne ressemble pas à un numéro de téléphone valide + Ceci ne ressemble pas à une adresse courriel valide + Renseignez une adresse courriel pour la récupération de compte. Utilisez ensuite un courriel ou un téléphone pour être éventuellement découvrable par les personnes qui vous connaissent. + Renseignez une adresse courriel pour la récupération de compte et pour être éventuellement découvrable par les personnes qui vous connaissent. + Courriel ou nom d’utilisateur + Envoyer le courriel de réinitialisation + Adresse courriel + + Vous avez retiré %1$s comme adresse pour ce salon. + Vous avez retiré %1$s comme adresses pour ce salon. + + + %1$s a retiré %2$s comme adresse pour ce salon. + %1$s a retiré %2$s comme adresses pour ce salon. + + + Vous avez ajouté %1$s comme adresse pour ce salon. + Vous avez ajouté %1$s comme adresses pour ce salon. + + + %1$s a ajouté %2$s comme adresse pour ce salon. + %1$s a ajouté %2$s comme adresses pour ce salon. + + + %1$s et 1 autre + %1$s et %2$d autres + + + %1$s, %2$s, %3$s et %4$d autre + %1$s, %2$s, %3$s et %4$d autres + + Êtes-vous sûr de vouloir supprimer tous les messages non envoyés dans ce salon \? + Supprimer les messages non envoyés + Messages non envoyés + Voulez-vous annuler l’envoi du message \? + Supprimer tous les messages en échec + Échec + Envoyé + Envoi + Événement d’état envoyé ! + Événement envoyé ! + Événement malformé + Type de message manquant + Pas de contenu + Clé d’état + Type + Modifier le contenu + Événements d’état + Explorer l’état du salon + Outils de développement + Voir les accusés de réception + Ne pas notifier + Notifier sans son + Notifier avec son + Message non envoyé suite à une erreur + Vérifié + Fermer le sélecteur d’émojis + Ouvrir le sélecteur d’émojis + Reconnu + Douteux + Niveau de confiance par défaut + Sélectionné + Vidéo + Ce salon a des brouillons non envoyés + Certains messages n’ont pas été envoyés + Supprimer l’avatar + Changer l’avatar + Image + Importer la clé depuis le fichier + Capture d’écran + Échec d’authentification + ${app_name} requiert que vous saisissiez vos identifiants à nouveau pour effectuer cette action. + Une nouvelle authentification est requise + Utilisateurs + Une erreur s’est produite lors du transfert de l’appel + Transférer + Rejoindre + Consulter d’abord + Appel en cours (%1$s) + Il y a eu une erreur lors de la recherche du numéro de téléphone + Pavé de numérotation + Rappeler + Cet appel est terminé + %1$s a refusé cet appel + Vous avez refusé cet appel %1$s + Vous êtes actuellement dans cet appel + %1$s a lancé un appel + Vous avez lancé un appel + Lien Matrix + Annuler les changements + Il y a des modifications non-enregistrées. Annuler les changements \? + Le salon n\'est pas encore créé. Annuler la création du salon \? + Le lien est malformé + Code QR non scanné ! + Code QR invalide (URI invalide) ! + Impossible avec vous-même ! + Partager par SMS + Impossible de trouver ce salon. Assurez-vous qu’il existe. + Impossible d’ouvrir un salon dont vous êtes banni. + Vérifiez cette session en confirmant que les chiffres suivants apparaissent sur l’écran de votre partenaire + Vérifiez cette session en confirmant que les émojis suivants apparaissent sur l’écran de votre partenaire + La vérification de cette session la marquera comme fiable, et marquera aussi votre session comme fiable pour votre partenaire. + Vérifiez cette session pour la marquer comme fiable. Faire confiance aux sessions de vos partenaires vous permet d’être plus serein en utilisant les messages chiffrés de bout en bout. + Demande de vérification entrante + Commencer la vérification + Pour une sécurité maximale, nous vous recommandons de faire cela en personne ou d’utiliser d’autres moyens de communication sécurisés. + Vérifier en comparant une courte chaîne de caractères. + Vous avez été déconnecté car vos identifiants sont incorrects ou ont expiré. + Utiliser la configuration + ${app_name} a détecté une configuration de serveur personnalisée pour le domaine de votre identifiant « %1$s » : +\n%2$s + Auto-compléter les options du serveur + Réponse de découverte du serveur d’accueil non valide + Signature + Algorithme + Version + Toutes les clés sont sauvegardées + Activer la sauvegarde sécurisée + On dirait que vous avez déjà configuré une sauvegarde de clé depuis une autre session. Voulez-vous la remplacer par celle que vous êtes en train de créer \? + Une sauvegarde est déjà disponible sur votre serveur d’accueil + La clé de récupération a été enregistrée. + La clé de récupération a bien été enregistrée vers « %s ». +\n +\nAttention : ce fichier pourrait être supprimé si l’application est désinstallée. + Enregistrer dans un fichier + Partager + Sauvegarder la clé de récupération + J’ai fait une copie + Terminé + Conservez votre clé de récupération dans un endroit sûr, comme un gestionnaire de mots de passe (ou un coffre-fort) + Votre clé de récupération est une mesure de précaution. Vous pouvez l’utiliser pour restaurer l’accès à vos messages chiffrés si vous oubliez votre phrase secrète. +\nConservez votre clé de récupération en lieu sûr, comme un gestionnaire de mots de passe (ou un coffre-fort) + Vos clés sont en cours de sauvegarde. + Bien joué ! + (Avancé) Configurer une clé de récupération + Ou protégez votre sauvegarde avec une clé de récupération, en la conservant en lieu sûr. + Création de la sauvegarde + Définir la phrase secrète + Nous conserverons une copie chiffrée de vos clés sur votre serveur d’accueil. Protégez votre sauvegarde avec une phrase secrète pour la sécuriser. +\n +\nPour une sécurité maximale, elle devrait être différente du mot de passe de votre compte. + Protégez votre sauvegarde avec une phrase secrète. + Exporter les clés manuellement + (Avancé) + Commencer à utiliser la sauvegarde de clés + Les messages dans les salons chiffrés sont sécurisés avec un chiffrement de bout en bout. Seuls vous et le(s) destinataire(s) avez les clés pour lire ces messages. +\n +\nSauvegardez vos clés de façon sécurisée pour éviter de les perdre. + Ne perdez jamais vos messages chiffrés + Aucune session Matrix n’est disponible + Veuillez supprimer la phrase secrète si vous voulez que ${app_name} génère une clé de récupération. + La phrase secrète est trop faible + Veuillez saisir une phrase secrète + Les phrases secrètes doivent concorder + Saisir la phrase secrète + Confirmer la phrase secrète + Créer la phrase secrète + Aucun APK des services Google Play valide n’a été trouvé. Les notifications peuvent ne pas fonctionner correctement. + %d+ + +%d + %1$s : %2$s + %1$s : + Seulement pour les erreurs + Pour les messages et les erreurs + Toujours + Afficher la zone d’information + réduire + développer + Désolé, une erreur est survenue + Votre serveur d’accueil ne prend pas encore en charge le chargement en différé des membres des salons. Réessayez ultérieurement. + Améliore les performances en ne chargeant les membres des salons qu’au premier affichage. + Charger les membres des salons en différé + Veuillez %s pour continuer à utiliser ce service. + Veuillez %s pour augmenter cette limite. + Ce serveur d’accueil a atteint sa limite mensuelle d’utilisateurs actifs. + Ce serveur d’accueil a atteint sa limite mensuelle d’utilisateurs actifs donc certains utilisateurs ne pourront pas se connecter. + Ce serveur d’accueil a dépassé une de ses limites de ressources. + Ce serveur d’accueil a dépassé une de ses limites de ressources donc certains utilisateurs ne pourront pas se connecter. + contacter l’administrateur de votre service + Contacter l’administrateur + Limite de ressources dépassée + Cliquer ici pour voir les anciens messages + Ce salon est la suite d’une autre conversation + La conversation continue ici + Ce salon a été remplacé et n’est plus actif + Veuillez renseigner votre mot de passe. + Veuillez renseigner un nom d’utilisateur. + Désactiver le compte + Pour poursuivre, veuillez renseigner votre mot de passe : + Veuillez oublier tous les messages que j’ai envoyé quand mon compte sera désactivé (Avertissement : les futurs utilisateurs verront une version incomplète des conversations) + Désactiver le compte + Voir maintenant + Pour continuer à utiliser le serveur d’accueil %1$s, vous devez lire et accepter les conditions générales. + Avatar + Avatar d’avertissement + Avatar de réception + Oublier le salon + Rejoindre + Motif : %1$s + %2$s vous a banni de %1$s + %2$s vous a expulsé de %1$s + L’administrateur de cette communauté n’a pas encore fourni de description. + Filtrer les salons du groupe + Filtrer les membres du groupe + Invité + Rejoint + Salons + Pas d’utilisateur + Salons + Personnes + Accueil + Exemple + Identifiant de communauté + Exemple + Nom de la communauté + Créer une communauté + Créer + Message chiffré + Notification sonore + Silencieuse + Désactivé + Le Markdown a été désactivé. + Le Markdown a été activé. + Pour réparer la gestion des applications Matrix + Markdown activé/désactivé + Change le nom d’affichage + Expulse l’utilisateur avec l’identifiant fourni + Définit le sujet du salon + Quitte le salon + Rejoint le salon avec l’alias fourni + Invite l’utilisateur avec l’identifiant fourni dans le salon actuel + Rétrograde l’utilisateur avec l’identifiant fourni + Définit le rang d’un utilisateur + Révoque le bannissement de l’utilisateur avec l’identifiant fourni + Exclus l’utilisateur avec l’identifiant fourni + Affiche une action + La commande « %s » nécessite plus de paramètres, ou certains paramètres ne sont pas corrects. + Commande non reconnue : %s + Erreur de commande + L’appel en téléconférence est en cours de développement et peut ne pas être fiable. + Attention ! + Ignorer + Ignorer la demande + Demande de partage de clé + Partager + Partager sans vérifier + Vérifier + Commencer la vérification + Une session non vérifiée demande les clés de chiffrement. +\nNom de la session : %1$s +\nVu : %2$s +\nSi vous ne vous êtes pas connecté à une autre session, ignorez cette demande. + Votre session non vérifiée « %s » demande les clés de chiffrement. + Une nouvelle session demande les clés de chiffrement. +\nNom de la session : %1$s +\nVu : %2$s +\nSi vous ne vous êtes pas connecté à une autre session, ignorez cette demande. + Vous avez ajouté une nouvelle session « %s », qui demande les clés de chiffrement. + Pour continuer, vous devez accepter les conditions de ce service. + Cette option nécessite une autre application pour enregistrer les messages. + Envoyer des messages vocaux + Utiliser la touche Entrée du clavier pour envoyer un message + Lancer l’appareil photo du système plutôt que l’écran d’appareil photo personnalisé. + Utiliser la caméra de l’appareil + Gérer les intégrations + Ajouter des applications Matrix + Aucun gestionnaire d’intégrations n’est configuré. + Un paramètre n’est pas valide. + Un paramètre requis est manquant. + Le salon %s n’est pas visible. + user_id manquant dans la requête. + room_id manquant dans la requête. + Vous n’avez pas la permission de faire cela dans ce salon. + Vous n’êtes pas dans ce salon. + Le rang doit être un entier positif. + Échec de l’envoi de la requête. + Lire des médias protégés par des DRM + Utiliser le micro + Utiliser la caméra + Tout bloquer + Autoriser + Partir de la téléconférence actuelle et basculer sur une autre \? + Désolé, les appels en visioconférence avec Jitsi ne sont pas pris en charge sur les anciens appareils (avec une version d\'Android antérieure à 6.0) + L’identifiant du salon + Votre thème + Votre identifiant utilisateur + L’URL de votre avatar + Votre nom d’affichage + Révoquer l’accès pour moi + Ouvrir dans le navigateur + Son utilisation peut entraîner le partage de données avec %s : + Widget + Widgets actifs + VUE + Créer des appels en téléconférence avec jitsi + Énorme + La plus grande + Très grande + Grande + Normale + Petite + Minuscule + Taille de la police + Rechercher dans l’historique + %1$s : %2$s %3$s + %1$s : %2$s + ** Échec de l’envoi − veuillez ouvrir le salon + Moi + Inviter un utilisateur par son identifiant Matrix + Utilisateurs Matrix uniquement + RÉPERTOIRE UTILISATEUR (%s) + CONTACTS LOCAUX (%d) + Inviter par identifiant + %1$s %2$s + %1$s et %2$s + "%1$s, " + Voulez-vous vraiment inviter %s à cette discussion \? + Motif + Révoquer le bannissement de l’utilisateur lui permettra de rejoindre le salon. + Bannir un utilisateur va l’expulser du salon et l’empêcher de le rejoindre à nouveau. + Révoquer le bannissement de l’utilisateur + Motif du bannissement + Bannir l’utilisateur + expulser un utilisateur le supprimera de ce salon. +\n +\nPour l’empêcher de revenir, vous devez plutôt le bannir. + Motif d’expulsion + Expulser l’utilisateur + Êtes-vous sûr de vouloir annuler l’invitation pour cet utilisateur \? + Annuler l’invitation + Afficher tous les messages de cet utilisateur + Ne plus ignorer cet utilisateur aura pour effet de ré-afficher ses messages. + Ne plus ignorer l’utilisateur + Ignorer + Se connecter avec l’authentification unique + Se connecter + Désolé, aucune application externe n’a été trouvée pour effectuer cette action. + continuer avec… + Prendre une vidéo + Prendre une photo + Prendre une photo ou une vidéo + Envoyer un autocollant + Envoyer des fichiers + Activer la HD + Désactiver la HD + Arrière + Frontale + Changer de caméra + Écouteurs sans fil + Écouteurs + Hauts-parleurs + Téléphone + Sélectionner un périphérique audio + Impossible d\'établir une connexion en temps réel. +\nVeuillez demander à l’administrateur de votre serveur d’accueil de configurer un serveur TURN afin que les appels fonctionnent de manière fiable. + Appel échoué + Ne plus me demander + Essayez d’utiliser %s + Demandez à l’administrateur de votre serveur d’accueil (%1$s) de configurer un serveur TURN afin que les appels fonctionnent de manière fiable. +\n +\nSinon, vous pouvez essayer d’utiliser le serveur public à %2$s, mais ça ne sera pas fiable et ça partagera votre adresse IP avec ce serveur. Vous pouvez aussi régler cela dans les paramètres. + L’appel a échoué en raison d’un serveur mal configuré + Envoyer un message vocal + Nouvel appel vidéo + Nouvel appel audio + Nouvelle discussion + Rechercher + URL du serveur d’identité + URL du serveur d’accueil + Se déconnecter + Se connecter + Créer un compte + Nom d’utilisateur + Rejoindre le salon + Lu + Envoyer dans + Avancement (%s %%) + L’envoi du rapport d’anomalie a échoué (%s) + Le rapport d’anomalie a bien été envoyé + Secouer avec agacement pour signaler une anomalie + Afin de diagnostiquer les problèmes, les journaux de ce client seront envoyés avec ce rapport d’erreur. Ce rapport d’erreur, y compris les journaux et la capture d’écran, ne seront pas visibles publiquement. Si vous préférez envoyer le texte ci-dessus uniquement, veuillez décocher : + Décrivez votre problème ici + Si possible, veuillez écrire la description en anglais. + Rapporter une erreur + Envoyer une capture d’écran + Inclure l’historique d’échange de clés + Envoyer les journaux d’erreur + Envoyer les journaux + Aucun groupe + Communautés + Inviter + Afficher tous les salons dans le répertoire, y compris ceux au contenu choquant. + Afficher les salons au contenu choquant + Répertoire des salons + Aucun salon public disponible + Aucun salon + Répertoire de salons + Salons + Aucun serveur d’identité configuré. + Plus aucun résultat + Aucun résultat + Vous n’avez pas autorisé ${app_name} à accéder à votre carnet d’adresses + Aucune discussion + Contacts Matrix uniquement + Répertoire utilisateur + Carnet d’adresses local + Discussions + Alertes système + Priorité basse + Invitations + Filtrer les noms des communautés + Filtrer les noms des salons + Filtrer les participants + Filtrer les favoris + Filtrer les noms des salons + Communautés + Salons + Participants + Favoris + Notifications + Accueil + Nouvelle valeur + Succès + Erreur + Attention + Confirmation + Revenir + Désactiver + Copié dans le presse-papiers + Dé-publier + Changer + Ajouter + Copier + Fermer + Ouvrir + Marquer comme lu + Réponse rapide + Historique + Tout marquer comme lu + Recherche globale + Appel vidéo + Appel audio + Se déconnecter + Actions + Quitter + Raccrocher + Refuser + Accepter + Refuser + Examiner + Ignorer + Annuler + Terminé + Passer + Accepter + Hors ligne + Inviter + ou + Envoyer quand même + Appeler quand même + Les téléconférences ne sont pas prises en charge dans les salons chiffrés + Informations sur la session + Vous ne pouvez pas passer un appel avec vous-même, attendez que les participants acceptent l’invitation + Vous ne pouvez pas passer un appel avec vous-même + Impossible de lancer l’appel + Les réunions utilisent les règles de sécurité et les permissions de Jitsi. Toutes les personnes actuellement dans ce salon recevront une invitation à participer à la réunion. + Commencer une téléconférence audio + Commencer une téléconférence vidéo + Vous n’avez pas la permission de lancer un appel + Vous n\'avez pas la permission de lancer un appel dans ce salon + Vous n’avez pas la permission de lancer une téléconférence + Vous n’avez pas la permission de lancer une téléconférence dans ce salon + Vous avez besoin de la permission d’invitation pour lancer une téléconférence dans ce salon + En raison de permissions manquantes, cette action n’est pas possible. + En raison de permissions manquantes, certaines fonctionnalités peuvent être absentes… + Impossible d’initier l’appel, réessayez plus tard + vidéo + audio + Téléconférence en cours. +\nLa rejoindre en %1$s ou en %2$s + Commencer une conversation + Réinitialiser + Ignorer + Pause + Lancer + Appel en cours + Signaler le contenu + Déconnecter + Révoquer + Aucun + Renommer + Supprimer + Afficher la source déchiffrée + Afficher la source + Permalien + Transférer + Plus tard + Effacer + Parler + Partager + Télécharger + Citer + Effacer + Renvoyer + Envoyer + Rester + Quitter + Enregistrer + Annuler + OK + Chargement… + Licences tierces + Vous n’aurez plus accès à vos messages chiffrés, sauf si vous sauvegardez vos clés avant de vous déconnecter. + Sauvegarder + Utiliser la sauvegarde de clés + Sauvegarde de clés… + Sauvegarde de clés… + Gérer la sauvegarde de clés + Nouvelles clés de message sécurisé + Utiliser la sauvegarde de clés + Ne perdez jamais vos messages chiffrés + Sécurité contre la perte d’accès aux messages et données chiffrées + Sauvegarde sécurisée + Commencer à utiliser la sauvegarde de clés + Ne perdez jamais vos messages chiffrés + C’est bien moi + Une nouvelle sauvegarde de clés de messages sécurisés a été détectée. +\n +\nSi vous n’avez pas configuré de nouvelle méthode de récupération, un attaquant essaye peut-être d’accéder à votre compte. Modifiez le mot de passe de votre compte et configurez une nouvelle méthode de récupération immédiatement dans les paramètres. + Nouvelle sauvegarde de clés + Supprimer les clés de chiffrement sauvegardées sur le serveur \? Vous ne pourrez plus utiliser votre clé de récupération pour lire l’historique des messages chiffrés. + Supprimer la sauvegarde + Vérification de l’état de la sauvegarde + Échec lors de la suppression de la sauvegarde (%s) + Suppression de la sauvegarde… + Pour utiliser la sauvegarde de clés sur cette session, faites une restauration avec votre phrase secrète ou votre clé de récupération. + Échec de la récupération des informations de confiance de la sauvegarde (%s). + La sauvegarde a une signature non valide de la session non vérifiée %s + La sauvegarde a une signature non valide de la session vérifiée %s + La sauvegarde a une signature valide de la session non vérifiée %s + La sauvegarde a une signature valide de la session vérifiée %s. + La sauvegarde a une signature valide de cette session. + La sauvegarde a une signature d’une session inconnue ayant pour identifiant %s. + Vos clés ne sont pas sauvegardées depuis cette session. + La sauvegarde de clés n’est pas activée sur cette session. + La sauvegarde de clés a été correctement configurée pour cette session. + Supprimer la sauvegarde + Restaurer depuis la sauvegarde + La cryptographie de la session n’est pas activée + Échec de récupération de la dernière version des clés de récupération (%s). + Sauvegarde restaurée %s ! + La sauvegarde n’a pas pu être déchiffrée avec cette clé de récupération : veuillez vérifier que vous avez saisi la bonne clé de récupération. + Veuillez saisir une clé de récupération + Déverrouiller l’historique + Importation des clés… + Téléchargement des clés… + Traitement de la clé de récupération… + Restauration de la sauvegarde : + Erreur de réseau : veuillez vérifier votre connexion et réessayer. + La sauvegarde n’a pas pu être déchiffrée avec cette phrase secrète. Veuillez vérifier que vous avez saisi la bonne phrase secrète de récupération. + Vous avez perdu votre clé de récupération \? Vous pouvez en configurer une autre dans les paramètres. + Récupération des messages + Saisir la clé de récupération + Utilisez votre clé de récupération pour déverrouiller l’historique de vos messages chiffrés + Si vous ne connaissez pas votre phrase secrète de récupération, vous pouvez %s. + utiliser votre clé de récupération + Utilisez votre phrase secrète de récupération pour déverrouiller l’historique de vos messages chiffrés + Récupération de la version de la sauvegarde… + Vous pourriez perdre l’accès vos messages si vous vous déconnectez ou si vous perdez votre appareil. + En êtes-vous sûr \? + Vos clés de chiffrement sont sauvegardées en arrière-plan sur votre serveur d’accueil. La sauvegarde initiale peut prendre plusieurs minutes. + La sauvegarde a commencé + Erreur inattendue + Clé de récupération + Génération de la clé de récupération utilisant la phrase secrète. Cette opération peut prendre plusieurs secondes. + Partager la clé de récupération avec… + Veuillez en faire une copie + Arrêter + Remplacer + Donner la permission + ${app_name} doit garder une connexion à faible empreinte en arrière-plan afin d’avoir des notifications fiables. +\nSur l’écran suivant on vous demandera d’autoriser ${app_name} à toujours fonctionner en arrière-plan, veuillez accepter. + Connexion en arrière-plan + Choisir une autre option + Donner la permission + ${app_name} peut fonctionner en arrière-plan pour gérer vos notifications de façon sécurisée et confidentielle. Cela peut affecter l’utilisation de la batterie. + Confidentialité des notifications + Gérer vos paramètres de découverte. + Découverte + Désactiver mon compte + Désactiver le compte + Ceci remplacera votre clé ou phrase actuelle. + Générer une nouvelle clé de sécurité ou définir une nouvelle phrase de sécurité pour votre sauvegarde existante. + Protection contre la perte d\'accès aux messages et données chiffrées en sauvegardant les clés de chiffrage sur votre serveur. + Activer sur cet appareil + Réinitialiser la sauvegarde sécurisée + Activer la sauvegarde sécurisée + Gérer + Sauvegarde sécurisée + Ajoute un bouton sur le compositeur pour ouvrir le clavier des emojis + Afficher le clavier des emojis + Le bouton Entrée sur le clavier logiciel enverra le message au lieu d’aller à la ligne + Envoyer le message avec Entrée + Afficher un aperçu du média avant de l’envoyer + Vibrer lorsque l’on mentionne un utilisateur + Cela inclut les changements d’avatar et de nom d’affichage. + Les invitations, expulsions et exclusions ne sont pas concernés. + Afficher les notifications d’arrivée et de départ + Utilisez la commande /confetti ou envoyez un message contenant ❄️ ou 🎉 + Afficher les animations de conversation + Cliquer sur les accusés de réception pour une liste détaillée. + Afficher les accusés de réception + Afficher l’horodatage au format 12 heures + Afficher l’horodatage pour tous les messages + Rédiger les messages en utilisant la syntaxe Markdown. Cela permet d’utiliser des styles avancés comme les astérisques pour afficher du texte en italique. + Format Markdown + Les autres utilisateurs pourront voir que vous êtes en train d’écrire. + Envoyer des notifications de saisie + Afficher un aperçu des liens dans la discussion quand votre serveur d’accueil le permet. + Aperçu des liens + Sessions + Épingler les salons avec des messages non lus + Épingler les salons avec des notifications manquées + Page d’accueil + Pays pour le répertoire téléphonique + Autoriser l’accès aux contacts locaux + Contacts locaux + Cibles de notification + Gestion des clés cryptographiques + Chiffrement + Intégrations + Avancé + Autres + Utilisateurs ignorés + Notifications + Paramètres utilisateur + Vider le cache des médias + Vider le cache + Conserver les médias + Politique de confidentialité + Droits d’auteur + Licences tierces + Termes et conditions + Version de olm + Version + Délai entre chaque synchronisation + %s +\nLa synchronisation peut être retardée selon les ressources (batterie) ou l’état (veille) de l’appareil. + Intervalle de synchronisation préféré + Délai d’attente de la requête de synchronisation + Activer la synchronisation en arrière-plan + Lancer au démarrage + Échec de la mise à jour des paramètres. + Vous ne serez pas notifié des messages entrants quand l’application est en arrière-plan. + Aucune synchronisation en arrière-plan + ${app_name} se synchronisera en arrière-plan de façon périodique à un moment précis (configurable). +\nCela aura un impact sur l’utilisation des données mobiles et de la batterie, une notification permanente sera affichée indiquant que ${app_name} est à l’écoute des évènements. + Optimisé pour le temps réel + ${app_name} se synchronisera en arrière-plan de façon à préserver les ressources limitées de l’appareil (batterie). +\nSelon l’état des ressources de votre appareil, la synchronisation peut être retardée par le système d’exploitation. + Optimisé pour préserver la batterie + Mode de synchronisation en arrière-plan + Synchronisation en arrière-plan + Messages envoyés par un robot + Appels entrants + Quand je suis invité sur un salon + Messages dans les discussions de groupe + Messages dans les conversations privées + Messages contenant mon nom d’utilisateur + Messages contenant mon nom affiché + Choisir la couleur de la LED, les vibrations, le son… + Configurer les notifications silencieuses + Configurer les notifications d’appel + Configurer les notifications sonores + Allumer l’écran pendant 3 secondes + Activer les notifications pour cette session + Activer les notifications pour ce compte + Son de notification + • Les notifications n’afficheront pas le contenu des messages + • Les notifications contiennent des métadonnées et des messages + • Le contenu du message de notification est issu directement du serveur d’accueil Matrix + • Les notifications ne contiennent que des métadonnées + • Les notifications sont envoyées via Firebase Cloud Messaging + Les applications n’ont pas besoin d’être connectées au serveur d’accueil en arrière-plan, cela devrait diminuer l’utilisation de la batterie + Je ne veux pas de mes messages chiffrés + La sauvegarde de clés sécurisées devrait être activée pour toutes vos sessions afin d’éviter de perdre l’accès à vos messages chiffrés. + Sauvegarde de clés en cours. Si vous vous déconnectez maintenant, vous perdrez accès à vos messages chiffrés. + Vous perdrez vos messages chiffrés si vous vous déconnectez maintenant + La sauvegarde des clés n’est pas terminée, veuillez patienter… + Vérifier la session + Utiliser la sauvegarde de clé + Sauvegarde de clé + Envoyer un autocollant + Informations sur la communauté + Rapport d’anomalie + Historique + Informations sur ce participant + Paramètres + Salon + Messages + Notifications muettes + Notifications sonores + Écoute d’évènements + Synchronisation… + Initialisation du service + Thème noir + Thème sombre + Thème clair + Par défaut du système + Vous avez activé le chiffrement de bout en bout (algorithme %1$s inconnu). + %1$s a activé le chiffrement de bout en bout (algorithme %2$s inconnu). + Vous avez activé le chiffrement de bout en bout. + %1$s a activé le chiffrement de bout en bout. + Vous avez empêché les visiteurs de rejoindre le salon. + %1$s a empêché les visiteurs de rejoindre le salon. + Vous avez empêché les visiteurs de rejoindre le salon. + %1$s a empêché les visiteurs de rejoindre le salon. + Vous avez autorisé les visiteurs à venir ici. + %1$s a autorisé les visiteurs à venir ici. + Vous avez autorisé les visiteurs à rejoindre le salon. + %1$s a autorisé les visiteurs à rejoindre le salon. + Vous avez modifié les adresses de ce salon. + %1$s a modifié les adresses de ce salon. + Vous avez modifié les adresses principale et alternative de ce salon. + %1$s a modifié les adresses principale et alternative de ce salon. + Vous avez modifié les adresses alternatives de ce salon. + %1$s a modifié les adresses alternatives de ce salon. + Vous avez supprimé l’adresse principale de ce salon. + %1$s a supprimé l’adresse principale de ce salon. + Vous avez défini %1$s comme adresse principale pour ce salon. + %1$s a défini %2$s comme adresse principale pour ce salon. + Vous avez ajouté %1$s et supprimé %2$s comme adresses pour ce salon. + %1$s a ajouté %2$s et supprimé %3$s comme adresses pour ce salon. + Vous avez annulé l’invitation de %1$s. Raison : %2$s + %1$s a annulé l’invitation de %2$s. Raison : %3$s + Vous avez accepté l’invitation de %1$s. Raison : %2$s + %1$s a accepté l’invitation de %2$s. Raison : %3$s + Vous avez révoqué l’invitation de %1$s à rejoindre le salon. Raison : %2$s + %1$s a révoqué l’invitation de %2$s à rejoindre le salon. Raison : %3$s + Vous avez envoyé une invitation à %1$s à rejoindre le salon. Raison : %2$s + %1$s a envoyé une invitation à %2$s à rejoindre le salon. Raison : %3$s + Vous avez banni %1$s. Raison : %2$s + %1$s a banni %2$s. Raison : %3$s + Vous avez révoqué le bannissement de %1$s. Raison : %2$s + %1$s a révoqué le bannissement de %2$s. Raison : %3$s + Vous avez expulsé %1$s. Raison : %2$s + %1$s a expulsé %2$s. Raison : %3$s + Vous avez refusé l’invitation. Raison : %1$s + %1$s a refusé l’invitation. Raison : %2$s + Vous êtes parti. Raison : %1$s + %1$s est parti. Raison : %2$s + Vous êtes parti du salon. Raison : %1$s + %1$s est parti du salon. Raison : %2$s + Vous avez rejoint. Raison : %1$s + %1$s rejoint. Raison : %2$s + Vous avez rejoint le salon. Raison : %1$s + %1$s a rejoint le salon. Raison : %2$s + %1$s vous a invité. Raison : %2$s + Vous avez invité %1$s. Raison : %2$s + %1$s a invité %2$s. Raison : %3$s + Votre invitation. Raison : %1$s + Invitation de %1$s. Raison : %2$s + Vider la file d’envoi + Envoi du message… + Message envoyé + Synchronisation initiale : +\nImportation des données du compte + Synchronisation initiale : +\nImportation des communautés + Synchronisation initiale : +\nImportation des salons que vous avez quittés + Synchronisation initiale : +\nImportation des salons où vous avez été invité + Synchronisation initiale : +\nImportation des salons que vous avez rejoints + Synchronisation initiale : +\nImportation des salons + Synchronisation initiale : +\nImportation de la cryptographie + Synchronisation initiale : +\nImportation du compte… + Synchronisation initiale : +\nTéléchargement des données… + Synchronisation initiale : +\nEn attente de la réponse du serveur… + Salon vide (était %s) + Salon vide + %1$s, %2$s, %3$s et %4$s + %1$s, %2$s et %3$s + %1$s et %2$s + Invitation au salon + Invitation de %s + Numéro de téléphone + Il est impossible pour le moment de revenir dans un salon vide. + Erreur de Matrix + Erreur de réseau + Envoi du message impossible + Effacement impossible + L’appareil de l’expéditeur ne nous a pas envoyé les clés pour ce message. + ** Déchiffrement impossible : %s ** + %1$s de %2$s à %3$s + %1$s a modifié le rang de %2$s. + Vous avez modifié le rang de %1$s. + Personnalisé + Personnalisé (%1$d) + Défaut + Modérateur + Admin + Vous avez modifié la téléconférence + %1$s a modifié la téléconférence + Vous avez mis fin à la téléconférence + %1$s a mis fin à la téléconférence + Vous avez démarré la téléconférence + Téléconférence démarrée par %1$s + Vous avez accepté l’invitation de %1$s + %1$s a accepté l’invitation de %2$s + Vous avez révoqué l’invitation de %1$s + %1$s a révoqué l’invitation de %2$s + Vous avez révoqué l’invitation de %1$s à rejoindre le salon + %1$s a révoqué l’invitation de %2$s à rejoindre le salon + Vous avez invité %1$s + %1$s a invité %2$s + Vous avez envoyé une invitation à %1$s pour rejoindre le salon + %1$s a envoyé une invitation à %2$s pour rejoindre le salon + Vous avez mis à jour votre profil %1$s + %1$s a mis à jour son profil %2$s + Message supprimé par %1$s [motif : %2$s] + Message supprimé [motif : %1$s] + Message supprimé par %1$s + Message supprimé + Vous avez supprimé l’avatar du salon + %1$s a supprimé l’avatar du salon + Vous avez supprimé le sujet du salon + %1$s a supprimé le sujet du salon + Vous avez supprimé le nom du salon + %1$s a supprimé le nom du salon + (l’avatar a aussi changé) + Téléconférence VoIP terminée + Téléconférence VoIP démarrée + Vous avez demandé une téléconférence VoIP + %1$s a demandé une téléconférence VoIP + Aucun changement. + • Les serveurs correspondant à des IP littérales sont maintenant interdits. + • Les serveurs correspondants à des IP littérales sont maintenant autorisés. + • Les serveurs correspondant à %s sont supprimés de la liste autorisée. + • les serveur correspondant à %s sont maintenant autorisés. + • Les serveurs correspondant à %s étaient supprimés de la liste des interdits. + • Les serveurs correspondant à %s sont maintenant interdits. + Vous avez changé les droits ACL du serveur pour ce salon. + %s a changé les droits ACL du serveur pour ce salon. + • Les serveurs correspondants à des IP littérales sont interdites. + • Les serveurs correspondants à des IP littérales sont autorisés. + • Les serveurs correspondant à %s sont autorisés. + • Les serveurs correspondant à %s sont interdits. + Vous avez paramétré les ACL pour ce salon. + %s paramètre les autorisations étendues (ACL) du serveur pour ce salon. + Vous avez mis cet endroit à niveau. + %s a mis cet endroit à niveau. + Vous avez mis à niveau ce salon. + %s a mis à niveau ce salon. + Vous avez activé le chiffrement de bout en bout (%1$s) + %1$s a activé le chiffrement de bout en bout (%2$s) + inconnu (%s). + n’importe qui. + tous les membres du salon. + tous les membres du salon, depuis qu’ils l’ont rejoint. + tous les membres du salon, depuis qu’ils ont été invités. + Vous avez rendu les futurs messages visible pour %1$s + %1$s a rendu les futurs messages visible pour %2$s + Vous avez rendu l’historique futur du salon visible pour %1$s + %1$s a rendu l’historique futur du salon visible pour %2$s + Vous avez raccroché. + %s a raccroché. + Vous avez répondu à l’appel. + %s a répondu à l’appel. + Vous avez envoyé les données pour configurer l’appel. + %s a envoyé les données pour configurer l’appel. + Vous avez passé un appel audio. + %s a passé un appel audio. + Vous avez passé un appel vidéo. + %s a passé un appel vidéo. + Vous avez changé le nom du salon en : %1$s + %1$s a changé le nom du salon en : %2$s + Vous avez modifié l’avatar du salon + %1$s a modifié l’avatar du salon + Vous avez changé le sujet en : %1$s + %1$s a changé le sujet en : %2$s + Vous avez supprimé votre nom d’affichage (précédemment %1$s) + %1$s a supprimé son nom d’affichage (précédemment %2$s) + Vous avez modifié votre nom d’affichage de %1$s en %2$s + %1$s a modifié son nom d’affichage de %2$s en %3$s + Vous avez modifié votre nom d’affichage en %1$s + %1$s a modifié son nom d’affichage en %2$s + Vous avez changé votre avatar + %1$s a changé d’avatar + Vous avez annulé l’invitation de %1$s + %1$s a annulé l’invitation de %2$s + Vous avez banni %1$s + %1$s a banni %2$s + Vous avez révoqué le bannissement de %1$s + %1$s a révoqué le bannissement de %2$s + Ignorer cet utilisateur aura pour effet de supprimer ses messages des salons que vous partagez. +\n +\nVous pouvez annuler cette action à tout moment dans les paramètres généraux. + Ignorer l’utilisateur + Rétrograder + Vous ne pourrez pas annuler cette modification car vous vous rétrogradez vous-même, il ne vous sera pas possible de regagner ces privilèges si vous êtes le dernier utilisateur privilégié de ce salon. + Vous rétrograder vous-même \? + Vous ne pourrez pas annuler cette modification car vous promouvez l’utilisateur au même rang que le vôtre. +\nLe voulez-vous vraiment \? + Afficher la liste des sessions + Mentionner + Nommer administrateur + Nommer modérateur + Réinitialiser en tant qu’utilisateur standard + Expulser + Révoquer le bannissement + Bannir + Exclure de ce salon + Quitter ce salon + Annuler l’invitation + Inviter + SESSIONS + Conversations privées + APPEL + OUTILS DE L’ADMINISTRATEUR + %1$s il y a %2$s + %1$s maintenant + Inactif + Hors ligne + En ligne + Créer + Voulez-vous vraiment bannir %s de cette discussion \? + Ce salon n’est pas public. Vous ne pourrez pas le rejoindre sans invitation. + Quitter le salon + 1 membre + Ajouter un membre + Nouvelle discussion + Ajoutez un serveur d’identité dans vos paramètres pour réaliser cette action. + Ceci est un aperçu du salon. Vous ne pouvez pas interagir. + un salon + Vous essayez d’accéder à %s. Souhaitez-vous rejoindre le salon pour participer à la discussion \? + %s vous a invité à rejoindre ce salon + Aller au premier message non lu. + Synchronisation… + Développer l’entête + Liste les membres + Rejeter + Aperçu + Rejoindre + Supprimer + Continuer + NON + OUI + Enregistré + Autoriser l’accès à vos contacts. + Pour scanner un code QR, vous devez autoriser l’accès à votre appareil photo. + Désolé. L’action n’a pas été réalisée faute d’autorisations + ${app_name} a besoin d’accéder à votre appareil photo et à votre microphone pour passer des appels vidéo. +\n +\nVeuillez autoriser l’accès dans les prochaines fenêtres pour pouvoir effectuer l’appel. + " +\n +\nVeuillez autoriser l’accès dans la prochaine fenêtre contextuelle pour pouvoir effectuer l’appel." + ${app_name} a besoin d’accéder à votre microphone pour passer des appels audio. + " +\n +\nVeuillez autoriser l’accès dans la prochaine fenêtre pour pouvoir effectuer l’appel." + ${app_name} a besoin d’accéder à votre appareil photo pour prendre des photos et passer des appels vidéo. + ${app_name} a besoin d’accéder à vos photos et vidéos pour envoyer et enregistrer des pièces jointes +\n +\nVeuillez autoriser l’accès dans la prochaine fenêtre pour pouvoir envoyer des fichiers depuis votre téléphone. + Information + Impossible d’enregistrer une vidéo + Prendre une photo ou une vidéo + Appel décroché ailleurs + Impossible d’initialiser l’appareil photo + Échec de la connexion + Le correspondant n’a pas décroché. + Vous avez mis l’appel en attente + %s a mis l’appel en attente + Mettre en attente + Reprendre + Retour à l’appel + Appel actif (%s) + Appel vidéo en cours… + Appel en cours… + Appel audio entrant + Appel vidéo entrant + Appel entrant + Appel… + Appel terminé + Appel en cours de connexion… + Appel établi + Appel + Sélectionner la sonnerie pour les appels : + Sonnerie d’appel entrant + Utilisera %s comme assistant quand votre serveur d’accueil n’en offre pas (votre adresse IP sera partagée lors d’un appel) + Autoriser le serveur d’assistance d’appel de secours + Utiliser la sonnerie par défaut de ${app_name} pour les appels entrants + Demander une confirmation avant de lancer un appel + Éviter les appels accidentels + Appels + Sujet du salon + Nom du salon + Aujourd’hui + Hier + %1$d min %2$d s + %d s + Petit + Moyen + Grand + Original + Envoyer en tant que + Liste des Groupes + Liste des accusés de lecture + Veuillez lancer ${app_name} sur un autre appareil qui peut déchiffrer le message pour qu’il puisse envoyer les clés à cette session. + Demande envoyée + Demande de clé envoyée. + Redemander les clés de chiffrement à vos autres sessions. + Ce nom d’utilisateur est déjà utilisé + Trop de requêtes ont été envoyées + Ne contient pas de JSON valide + JSON malformé + Le jeton d’accès fourni n’a pas été reconnu + Non autorisé, identifiants d’authentification valides manquants + Nom d’utilisateur/mot de passe invalide + Votre appareil utilise une version obsolète du protocole de sécurité TLS, vulnérable aux attaques. Pour votre sécurité vous ne pourrez pas vous connecter + Erreur SSL. + Erreur SSL : l\'identité du pair n\'a pas été vérifiée. + Impossible de joindre le serveur d’accueil à cette URL, veuillez la vérifier + Ce n’est pas une adresse de serveur Matrix valide + Cette URL est injoignable, veuillez la vérifier + Veuillez saisir une URL valide + Impossible de s’inscrire + Impossible de s’inscrire : erreur réseau + Impossible de se connecter + Impossible de se connecter : erreur réseau + L’URL doit commencer par http[s]:// + Veuillez lire et accepter les politiques de ce serveur d’accueil : + Votre mot de passe a été réinitialisé. +\n +\nVous avez été déconnecté de toutes les sessions et ne recevrez plus de notifications. Pour réactiver les notifications, reconnectez-vous sur chaque appareil. + Un nouveau mot de passe doit être saisi. + Serveur d’identité : + Serveur d’accueil : + Nom d’utilisateur déjà utilisé + Ce serveur d’accueil souhaite s’assurer que vous n’êtes pas un robot + Utiliser des options de serveur personnalisées (avancé) + Les mots de passe ne correspondent pas + Jeton non valide + Numéro de téléphone manquant + Le numéro de téléphone est déjà défini. + Mot de passe manquant + Mot de passe trop court (6 min) + Les noms d’utilisateurs ne peuvent contenir que des lettres, des nombres, des points, des traits d’union et des tirets bas + Nom d’utilisateur et/ou mot de passe incorrect + Confirmez votre nouveau mot de passe + Répéter le mot de passe + Numéro de téléphone (facultatif) + Numéro de téléphone + Renseignez un numéro de téléphone pour être éventuellement découvrable par les personnes qui vous connaissent. + Nom d’utilisateur + Nouveau mot de passe + Mot de passe + Retourner à l’écran de connexion + Ignorer + Valider + Créer un compte + Vous avez expulsé %1$s + %1$s a expulsé %2$s + Vous avez rejeté l’invitation + %1$s a rejeté l’invitation + Vous avez quitté le salon + %1$s a quitté le salon + Vous avez quitté le salon + %1$s est parti du salon + Vous avez rejoint le salon + %1$s a rejoint le salon + Vous avez rejoint le salon + %1$s a rejoint le salon + %1$s vous a invité + Vous avez invité %1$s + %1$s a invité %2$s + Vous avez créé la conversation + %1$s a créé la conversation + Vous avez créé le salon + %1$s a créé le salon + Votre invitation + invitation de %s + Vous avez envoyé un autocollant. + %1$s a envoyé un autocollant. + Vous avez envoyé une image. + %1$s a envoyé une image. + %1$s : %2$s \ No newline at end of file From 05993c36c4483eabfc2fafbe7710f598b81b5d13 Mon Sep 17 00:00:00 2001 From: Jeanne Lavoie Date: Sat, 10 Apr 2021 16:35:05 +0000 Subject: [PATCH 036/230] Translated using Weblate (French) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 1bb02454ec..29e0d1a881 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -427,7 +427,7 @@ Ne pas faire confiance Se déconnecter Ignorer - Information sur le salon + Informations sur le salon Participants Fichiers Paramètres @@ -606,8 +606,8 @@ Le rapport d’anomalie a bien été envoyé L’envoi du rapport d’anomalie a échoué (%s) Les noms d’utilisateurs ne peuvent contenir que des lettres, des nombres, des points, des traits d’union et des tirets bas - Cela ne ressemble pas à une adresse e-mail valide - Cela ne ressemble pas à un numéro de téléphone valide + Ceci ne ressemble pas à une adresse e-mail valide + Ceci ne ressemble pas à un numéro de téléphone valide Cette adresse e-mail est déjà utilisée. Veuillez vérifier votre e-mail pour continuer votre inscription L’inscription avec e-mail et numéro de téléphone à la fois n’est pas prise en charge tant que l’API n’existe pas. Seul le numéro de téléphone sera pris en compte. @@ -2629,7 +2629,7 @@ %s a mis l’appel en attente Mettre en attente Reprendre - Non autorisé, identifiants d\'authentification valides manquants + Non autorisé, identifiants d’authentification valides manquants Nouvelle valeur Revenir Dé-publier From 2563bc98794bca84c85157ddf44ad483b073d70c Mon Sep 17 00:00:00 2001 From: Oymate Date: Sat, 10 Apr 2021 08:05:45 +0000 Subject: [PATCH 037/230] Translated using Weblate (Bengali (Bangladesh)) Currently translated at 51.4% (1215 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/bn_BD/ --- vector/src/main/res/values-bn-rBD/strings.xml | 1381 ++++++++++++++++- 1 file changed, 1380 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-bn-rBD/strings.xml b/vector/src/main/res/values-bn-rBD/strings.xml index a6b3daec93..db2ee10bbc 100644 --- a/vector/src/main/res/values-bn-rBD/strings.xml +++ b/vector/src/main/res/values-bn-rBD/strings.xml @@ -1,2 +1,1381 @@ - \ No newline at end of file + + %1$s একটি ছবি পাঠিয়েছে। + একটি নতুন সুরক্ষিত বার্তা কুঞ্জি ব্যাকআপ সনাক্ত করা হয়েছে। +\n +\nআপনি যদি নতুন পুনরুদ্ধারের পদ্ধতি সেট না করে থাকেন তবে একজন আক্রমণকারী আপনার অ্যাকাউন্ট অ্যাক্সেস করার চেষ্টা করছেন। আপনার অ্যাকাউন্ট পাসওয়ার্ড পরিবর্তন করুন এবং সেটিংসে অবিলম্বে একটি নতুন পুনরুদ্ধার পদ্ধতি সেট করুন। + + %d টি নতুন কী এই ডিভাইসে যোগ করা হয়েছে। + %d টি নতুন কী এই ডিভাইসে যোগ করা হয়েছে। + + আপনার পুনরুদ্ধারের কুঞ্জি হারিয়ে গেছে\? আপনি সেটিংস একটি নতুন সেট আপ করতে পারেন। + আপনার পুনরুদ্ধার কী ব্যবহার করুন + আপনার এনক্রিপ্ট করা বার্তাগুলির ইতিহাস আনলক করতে আপনার পুনরুদ্ধার পাসফ্রেজটি ব্যবহার করুন + ব্যাকআপ সংস্করণ আনছে… + আপনি লগ আউট বা এই ডিভাইসটি হারাতে পারলে আপনি আপনার বার্তাগুলির অ্যাক্সেস হারিয়ে ফেলতে পারেন। + তুমি কি নিশ্চিত\? + আপনার এনক্রিপশন কীগুলি এখন ব্যাকগ্রাউন্ডে আপনার হোমসার্ভারে ব্যাক আপ করা হচ্ছে। প্রাথমিক ব্যাকআপটি কয়েক মিনিট সময় নিতে পারে। + ব্যাকআপ শুরু হয়েছে + অপ্রত্যাশিত ত্রুটি + পুনরুদ্ধার কী + পাসফ্রেজ ব্যবহার করে পুনরুদ্ধার কী তৈরি করা হচ্ছে, এই প্রক্রিয়াটি কয়েক সেকেন্ড সময় নিতে পারে। + এর সাথে পুনরুদ্ধার কী ভাগ করুন… + দয়া করে একটি অনুলিপি করুন + থামুন + প্রতিস্থাপন + দেখে মনে হচ্ছে আপনার অন্য সেশন থেকে ইতিমধ্যে সেটআপ কী ব্যাকআপ রয়েছে। আপনি কি এটি তৈরি করছেন তার সাথে প্রতিস্থাপন করতে চান\? + আপনার হোম সার্ভারে ইতিমধ্যে একটি ব্যাকআপ রয়েছে + পুনরুদ্ধার কী সংরক্ষণ করা হয়েছে। + পুনরুদ্ধার কীটি \'%s\' তে সংরক্ষণ করা হয়েছে। +\n +\nসতর্কতা: অ্যাপ্লিকেশনটি আনইনস্টল করা হলে এই ফাইলটি মুছে যেতে পারে। + ফাইল হিসাবে সংরক্ষণ করুন + অংশভাগ + রিকভারি কী সংরক্ষণ করুন + আমি একটি অনুলিপি তৈরি করেছি + সম্পন্ন + আপনার পুনরুদ্ধার কীটি কোথাও খুব সুরক্ষিত রাখুন যেমন একটি পাসওয়ার্ড পরিচালক (বা নিরাপদ) + আপনার পুনরুদ্ধার কীটি একটি সুরক্ষা জাল - আপনি যদি আপনার পাসফ্রেজ ভুলে যান তবে আপনি এটি আপনার এনক্রিপ্ট করা বার্তাগুলির অ্যাক্সেস পুনরুদ্ধার করতে ব্যবহার করতে পারেন। +\nআপনার পুনরুদ্ধার কীটি কোথাও খুব সুরক্ষিত রাখুন যেমন একটি পাসওয়ার্ড পরিচালক (বা নিরাপদ) + আপনার কীগুলি ব্যাক আপ করা হচ্ছে। + সাফল্য! + (উন্নত) পুনরুদ্ধার কী সহ সেট আপ করুন + বা, আপনার ব্যাকআপটিকে পুনরুদ্ধার কী দ্বারা সুরক্ষিত করুন, এটি কোথাও নিরাপদ সাশ্রয় করুন। + ব্যাকআপ তৈরি করা হচ্ছে + পাসফ্রেজ সেট করুন + আমরা আপনার হোমসভারে আপনার কীগুলির একটি এনক্রিপ্ট করা অনুলিপি সঞ্চয় করব। আপনার ব্যাকআপটিকে সুরক্ষিত রাখতে একটি পাসফ্রেজ দিয়ে সুরক্ষিত করুন। +\n +\nসর্বাধিক সুরক্ষার জন্য, এটি আপনার অ্যাকাউন্টের পাসওয়ার্ডের থেকে আলাদা হওয়া উচিত। + একটি পাসফ্রেজ দিয়ে আপনার ব্যাকআপ সুরক্ষিত করুন। + ম্যানুয়ালি কী রপ্তানি করুন + (উন্নত) + কী ব্যাকআপ ব্যবহার শুরু করুন + এনক্রিপ্ট করা কক্ষের বার্তাগুলি শেষ-থেকে-শেষ এনক্রিপশন সহ সুরক্ষিত। এই বার্তাগুলি পড়ার জন্য কেবলমাত্র আপনার এবং প্রাপকের (গুলি) কী রয়েছে। +\n +\n আপনার কীগুলি এড়াতে নিরাপদে ব্যাক আপ দিন। + এনক্রিপ্ট করা বার্তাগুলি কখনই হারাবেন না + কোনও ম্যাট্রিক্স সেশন উপলব্ধ নেই + আপনি যদি এলিমেন্টটি পুনরুদ্ধার কী তৈরি করতে চান তবে দয়া করে পাসফ্রেজটি মুছুন। + পাসফ্রেজটি খুব দুর্বল + দয়া করে একটি পাসফ্রেজ লিখুন + পাসফ্রেজ মেলে নি + পাসফ্রেজ প্রবেশ করুন + পাসফ্রেজ নিশ্চিত করুন + পাসফ্রেজ তৈরি করুন + কোনও বৈধ গুগল প্লে পরিষেবা APK পাওয়া যায় নি। বিজ্ঞপ্তিগুলি সঠিকভাবে কাজ করতে পারে না। + %d+ + +%d + %1$s: %2$s + %1$s: + শুধুমাত্র ত্রুটির জন্য + বার্তা এবং ত্রুটির জন্য + সর্বদা + তথ্য অঞ্চলটি দেখান + বন্ধ + সম্প্রসারিত + দুঃখিত, একটি ত্রুটি ঘটেছে + আপনার হোমসার্ভারটি এখনও সদস্যদের অলস লোডিং সমর্থন করে না। পরে চেষ্টা করুন। + প্রথম দর্শনে কেবল রুমের সদস্যদের লোড করে কর্মক্ষমতা বাড়ান। + রুম সদস্যরাদের অলস লোড করুন + এই পরিষেবাটি ব্যবহার চালিয়ে যেতে দয়া করে %s করুন। + এই সীমাটি বৃদ্ধি পেতে দয়া করে %s করুন। + এই হোমসার্ভারটি তার মাসিক সক্রিয় ব্যবহারকারীর সীমাতে ছুঁয়েছে। + এই হোমসার্ভারটি তার মাসিক অ্যাক্টিভ ব্যবহারকারীর সীমাটিতে আঘাত করেছে তাই কিছু ব্যবহারকারী লগ ইন করতে পারবেন না + এই হোমসার্ভারটি এর অন্যতম এক রিসোর্স সীমা অতিক্রম করেছে। + এই হোমসার্ভারটি এর একটি রিসোর্স সীমা অতিক্রম করেছে সুতরাং কিছু ব্যবহারকারী লগ ইন করতে পারবেন না + আপনার পরিষেবা প্রশাসকের সাথে যোগাযোগ করুন + প্রশাসকের সাথে যোগাযোগ করুন + সংস্থান সীমা অতিক্রম করেছে + পুরানো বার্তা দেখতে এখানে ক্লিক করুন + এই ঘরটি অন্য কথোপকথনের একটি ধারাবাহিকতা + কথোপকথন এখানে অবিরত + এই ঘরটি প্রতিস্থাপন করা হয়েছে এবং এটি আর সক্রিয় নেই + আপনার পাসওয়ার্ড দিন। + দয়া করে একটি ব্যবহারকারীর নাম লিখুন। + অ্যাকাউন্ট নিষ্ক্রিয় করুন + চালিয়ে যেতে, দয়া করে আপনার পাসওয়ার্ড লিখুন: + আমার অ্যাকাউন্টটি নিষ্ক্রিয় করার সময় আমি যে সমস্ত বার্তা প্রেরণ করেছি তা ভুলে যান (সতর্কতা: এটি ভবিষ্যতের ব্যবহারকারীদের কথোপকথনের একটি অসম্পূর্ণ দৃশ্য দেখতে পাবে) + এটি আপনার অ্যাকাউন্টকে স্থায়ীভাবে অকেজো করে তুলবে। আপনি লগ ইন করতে সক্ষম হবেন না এবং কেউ একই ব্যবহারকারীর আইডি পুনরায় নিবন্ধন করতে সক্ষম হবেন না। এটি আপনার অ্যাকাউন্টের অংশ নিচ্ছে এমন সমস্ত কক্ষ ছেড়ে দেবে এবং এটি আপনার পরিচয় সার্ভার থেকে আপনার অ্যাকাউন্টের বিশদ সরিয়ে দেবে। এই ক্রিয়াটি অপরিবর্তনীয় । +\n +\nআপনার অ্যাকাউন্টটি নিষ্ক্রিয় করা ডিফল্টরূপে আমাদের পাঠানো বার্তাগুলি ভুলে যাওয়ার কারণ নয় । আপনি যদি আমাদের বার্তা ভুলে যেতে চান তবে দয়া করে নীচের বাক্সটি টিক দিন। +\n +\nম্যাট্রিক্সে বার্তার দৃশ্যমানতা ইমেলের অনুরূপ। আমাদের আপনার বার্তাগুলি ভুলে যাওয়ার অর্থ হ\'ল আপনি যে বার্তাগুলি প্রেরণ করেছেন সেগুলি কোনও নতুন বা নিবন্ধিত ব্যবহারকারীদের সাথে ভাগ করা হবে না, তবে নিবন্ধিত ব্যবহারকারীরা যাদের ইতিমধ্যে এই বার্তাগুলিতে অ্যাক্সেস রয়েছে তাদের কপিটিতে এখনও অ্যাক্সেস থাকবে। + অ্যাকাউন্ট নিষ্ক্রিয় করুন + এখনই পর্যালোচনা করুন + "%1$s হোমসার্ভারটি ব্যবহার চালিয়ে যেতে আপনাকে অবশ্যই শর্তাদিটি কে পর্যালোচনা এবং সম্মত করতে হবে।" + আবতার + অবতার নোট করুন + রিসিপ্ট অবতার + ঘর ভুলে যান + পুনরায় যুক্ত + কারণ: %1$s + %2$s দ্বারা আপনাকে %1$s থেকে নিষিদ্ধ করা হয়েছে। + আপনি %1$s থেকে %2$s দ্বারা লাথি খেয়েছেন + সম্প্রদায় প্রশাসক এই সম্প্রদায়ের জন্য দীর্ঘ বিবরণ সরবরাহ করেনি। + + ১ টি ঘর + %d টি ঘর + + + ১ জন সদস্য + %d জন সদস্য + + গোষ্ঠীর ঘরগুলো ফিল্টার করুন + গোষ্ঠীর সদস্যদের ফিল্টার করুন + আমন্ত্রিত + যোগ করেছেন + ঘরগুলি + কোন ব্যবহারকারি নেই + ঘরগুলি + লোক + ঘর + উদাহরণ + সম্প্রদায়ের আইডি + উদাহরণ + সম্প্রদায়ের নাম + সম্প্রদায় তৈরি করুন + সৃষ্টি + এনক্রিপ্ট করা বার্তা + সশব্দ + নিঃশব্দ + অফ + মার্কডাউন অক্ষম করা হয়েছে। + মার্কডাউন সক্ষম করা হয়েছে। + ম্যাট্রিক্স অ্যাপ্লিকেশন ব্যবস্থাপনা ঠিক করতে + অন/অফ মার্কডাউন + আপনার প্রদর্শনের ডাকনাম পরিবর্তন করে + প্রদত্ত আইডি সহ ব্যবহারকারীকে কিক্ করে + রুমের টপিক সেট করুন + কক্ষ ছেড়ে দিন + প্রদত্ত ওরফে সহ রুমে যোগ দেয় + প্রদত্ত আইডি সহ ব্যবহারকারীকে বর্তমান কক্ষ এ আমন্ত্রণ জানায় + প্রদত্ত আইডি সহ ব্যবহারকারীকে ডিওপ করে + ব্যবহারকারীর পাওয়ার স্তর নির্ধারণ করুন + প্রদত্ত আইডি সহ ব্যবহারকারীকে নিষিদ্ধ তালিকা থেকে বের করে + দেওয়া আইডি সঙ্গে ব্যবহারকারী কে নিষিদ্ধ করে + কর্ম প্রদর্শন করে + কমান্ড \"%s\" আরও পেরামিটার প্রয়োজন, অথবা কিছু পেরামিটার ভুল। + অজ্ঞাত কম্যান্ড: %s + কম্যান্ড এর ত্রুটি + কনফারেন্স কল এখনো ডেভেলপমেন্ট এ আছে আর নির্ভরযোগ্য নাও হতে পারে। + সতর্কবাণী! + উপেক্ষা + অনুরোধ উপেক্ষা করুন + কুঞ্জি ভাগ অনুরোধ + ভাগ + যাচাই না করে শেয়ার করুন + যাচাই + যাচাই শুরু করুন + একটি যাচাইকরণ সেশান এনক্রিপশন কুঞ্জি অনুরোধ করছে। +\nসেশানের নাম: %1$s +\nশেষ দেখা হয়েছে: %2$s +\nআপনি অন্য সেশানে লগ ইন না করলে, এই অনুরোধটি উপেক্ষা করুন। + আপনার যাচাই না করা সেশান \'%s\' এনক্রিপশন কুঞ্জি অনুরোধ করছে। + একটি নতুন সেশান এনক্রিপশন কুঞ্জি অনুরোধ করছে। +\nসেশানের নাম: %1$s +\nশেষ দেখা হয়েছে: %2$s +\nআপনি অন্য সেশানে লগ ইন না করলে, এই অনুরোধটি উপেক্ষা করুন। + আপনি একটি নতুন সেশান \'%s\' যোগ করেছেন, যা এনক্রিপশন কীগুলির জন্য অনুরোধ করছে। + চালিয়ে যেতে আপনার এই পরিষেবার শর্তাদি স্বীকার করতে হবে। + এই বিকল্প বার্তা রেকর্ড করার জন্য একটি তৃতীয় পক্ষের অ্যাপ্লিকেশন প্রয়োজন। + ভয়েস বার্তা পাঠান + বার্তা পাঠাবার জন্য কীবোর্ড এর এন্টার বোতাম প্রয়োগ করুন + কাস্টম ক্যামেরা স্ক্রীনের পরিবর্তে সিস্টেম ক্যামেরাটি শুরু করে। + নেটিভ ক্যামেরা ব্যবহার করুন + ম্যাট্রিক্স এপ্লিকেশনগুলি যোগ করুন + কোনও ইন্টিগ্রেশন ম্যানেজার কনফিগার করা নেই। + একটি পেরামিটার বৈধ নয়। + একটি প্রয়োজনীয় প্যারামিটারটি অনুপস্থিত আছে। + রুম %s দৃশ্যমান নয়। + অনুরোধে user_id অনুপস্থিত। + অনুরোধে room_id অনুপস্থিত। + আপনি এই রুমে এটা করার অনুমতি নেই। + আপনি এই রুমে নেই। + শক্তি স্তর ইতিবাচক পূর্ণসংখ্যা হতে হবে। + অনুরোধ পাঠাতে ব্যর্থ। + উইজেট তৈরি করতে অক্ষম। + ডিআরএম সুরক্ষিত মিডিয়া পড়ুন + মাইক্রোফোন ব্যবহার করুন + ক্যামেরা ব্যবহার করুন + সব অবরুদ্ধ + অনুমতি + এই উইজেটটি নিম্নলিখিত সংস্থানগুলি ব্যবহার করতে চায়: + দুঃখিত, জিটসির সাথে কনফারেন্স কলগুলি পুরোনো ডিভাইসগুলিতে সমর্থিত নয় (৬.০ এর নীচে এন্ড্রোইড অপারেটিং সিস্টেম সহ ডিভাইসগুলি) + কক্ষের আইডি + উইজেট আইডি + আপনার থিম + আপনার ব্যবহারকারীর আইডি + আপনার অবতার URL + আপনার প্রদশনীয় নাম + আমার জন্য অ্যাক্সেস প্রত্যাহার করুন + ব্রাউজারে খুলুন + উইজেট পুনরায় লোড করুন + উইজেট লোড করতে ব্যর্থ। +\n%s + এটি ব্যবহার করে ডেটা %s এর সাথে ভাগ করে নিতে পারে: + এটি ব্যবহার করে কুকিজ সেট করতে পারে এবং %s এর সাথে ডেটা ভাগ করা যায়: + এই উইজেটটি যুক্ত করেছেন: + উইজেট লোড করুন + উইজেট + সক্রিয় উইজেটগুলি + দেখুন + + ১ টি সক্রিয় উইজেট + %d টা সক্রিয় উইজেট + + আপনি কি এই রুমে উইজেট মুছে ফেলতে চান\? + Jitsi সঙ্গে কনফারেন্স কল তৈরি করুন + উইজেট এর নির্মাণ ব্যর্থ হয়েছে + আপনি এই রুমে উইজেট পরিচালনা করার অনুমতি প্রয়োজন + বিপুল + বৃহত্তম + বৃহত্তর + বড় + সাধারন + ছোট + অতি ক্ষুদ্র + অক্ষর এর আকার + ঐতিহাসিক জন্য অনুসন্ধান করুন + %1$s: %2$s %3$s + %1$s: %2$s + ** পাঠাতে ব্যর্থ - দয়া করে রুম খুলুন + আমি + নতুন আমন্ত্রণগুলি + নতুন বার্তাগুলি + রুম + নতুন ইভেন্ট + %2$s এ %1$s + + %d টি বিজ্ঞপ্তি + %d টি বিজ্ঞপ্তি + + + %1$s: ১ টি বার্তা + %1$s: %2$d টি বার্তা + + + ১ টি রুম + %d টি রুম + + পটভূমি সিঙ্ক মোড (পরীক্ষামূলক) + পটভূমি সিঙ্ক্রোনাইজেশন + বোট দ্বারা পাঠানো বার্তাগুলি + কল এর আমন্ত্রণগুলি + যখন আমি একটি রুম আমন্ত্রিত + গ্রুপ চ্যাটে বার্তা + এক থেকে এক চ্যাট বার্তা + আমার ব্যবহারকারী নাম ধারণকারী বার্তা + আমার প্রদর্শন নাম ধারণকারী বার্তা + "LED\'র রং, কম্পন, শব্দ নির্বাচন করুন…" + নীরব বিজ্ঞপ্তিগুলি কনফিগার করুন + কল বিজ্ঞপ্তিগুলি কনফিগার করুন + হাল্লা বিজ্ঞপ্তিগুলি কনফিগার করুন + স্ক্রিন টি ৩ সেকেন্ডের জন্য চালু করুন + এই সেশানের জন্য বিজ্ঞপ্তি সক্রিয় করুন + এই অ্যাকাউন্টের জন্য বিজ্ঞপ্তি সক্রিয় করুন + বিজ্ঞপ্তি শব্দ + • বিজ্ঞপ্তিগুলি বার্তা সামগ্রী দেখাবে না + • বিজ্ঞপ্তিগুলি তে মেটা এবং বার্তা ডেটা থাকে + • বিজ্ঞপ্তিটির বার্তা সামগ্রী ম্যাট্রিক্স হোমসার্ভার থেকে নিরাপদে সরাসরি অবস্থিত হয় + • বিজ্ঞপ্তিগুলি তে শুধুমাত্র মেটাডেটা থাকে + • বিজ্ঞপ্তি Firebase ক্লাউড মেসেজিং মাধ্যমে পাঠানো হয় + অ্যাপগুলি ব্যাকগ্রাউন্ডে হোম সার্ভারের সাথে সংযোগ করার প্রয়োজন করার প্রয়োজন নেই , এটি ব্যাটারির ব্যবহারকে কমাতে পারে + অ্যাপ্লিকেশন ব্যাকগ্রাউন্ডে চালানোর অনুমতি প্রয়োজন + হ্রাস গোপনীয়তা + সাধারণ + অপ্টিমাইজেশান অবহেলা + যদি কোনও ব্যবহারকারী কোনও ডিভাইসটিকে নির্দিষ্ট সময়ের জন্য আনপ্লাগ এবং স্থিতিশীল রাখে তবে স্ক্রীন বন্ধের সাথে ডিভাইসটি ডোজ মোডে প্রবেশ করে। এটি অ্যাপ্লিকেশানগুলিকে নেটওয়ার্ক অ্যাক্সেস করতে বাধা দেয় এবং তাদের কাজ, সিঙ্ক এবং মান অ্যালার্মগুলি স্থগিত করে। + ${app_name} ব্যাটারি অপ্টিমাইজেশান দ্বারা প্রভাবিত হয় না। + ব্যাটারি অপ্টিমাইজেশান + সীমাবদ্ধগুলি নিষ্ক্রিয় + ব্যাকগ্রউন্ডের সীমাবদ্ধতা রিমোট এর জন্য সক্রিয় করা হয়েছে। +\nঅ্যাপ্লিকেশন যেটি করার চেষ্টা করে সেটি ব্যাকগ্রাউন্ডে থাকা অবস্থায় আক্রমনাত্মকভাবে সীমিত হবে এবং এটি বিজ্ঞপ্তিগুলিতে প্রভাবিত হতে পারে। +\n%1$s + ব্যাকগ্রউন্ডের সীমাবদ্ধতা ${app_name} এর জন্য নিষ্ক্রিয় করা হয়েছে। এই পরীক্ষা মোবাইল ডেটা ব্যবহার করে চালানো উচিত (ওয়াইফাই না)। +\n%1$s + ব্যাকগ্রাউন্ড এর সীমাবদ্ধতা চেক করুন + বুট থেকে শুরু করা সক্রিয় করুন + ডিভাইসটি পুনরায় চালু হওয়ার সময় পরিষেবাটি শুরু হবে না, আপনি একবার ${app_name} টি খোলা না হওয়া পর্যন্ত বিজ্ঞপ্তি পাবেন না। + ডিভাইসটি পুনরায় চালু হলে পরিষেবা শুরু হবে। + বুট করার সময় শুরু + পরিষেবা পুনরায় আরম্ভ করতে ব্যর্থ হয়েছে + সেবা টি হত্যা করা হয়েছে এবং স্বয়ংক্রিয়ভাবে পুনরায় আরম্ভ করা হয়েছে। + বিজ্ঞপ্তি পরিষেবা অটো-পুনরায়-আরম্ভ + পরিসেবা আরম্ভ + বিজ্ঞপ্তিগুলির সেবা চলছে না। +\nঅপ্প্লিকেশনটি পুনরায় আরম্ভ করার চেষ্টা করুন। + বিজ্ঞপ্তিগুলির সেবা চলছে। + বিজ্ঞপ্তিগুলির সেবা + হোম সার্ভারে FCM টোকেন নিবন্ধন করতে ব্যর্থ হয়েছে: +\n%1$s + FCM টোকেন সফলভাবে হোম সার্ভারে নিবন্ধিত। + টোকেন নিবন্ধন + একাউন্ট যোগ করুন + [%1$s] +\nএই ত্রুটি ${app_name} এর নিয়ন্ত্রণের বাইরে। ফোনে কোন গুগল একাউন্ট নেই। অ্যাকাউন্ট ম্যানেজার খুলুন এবং একটি গুগল একাউন্ট যোগ করুন। + [%1$s] +\nএই ত্রুটি ${app_name} এর নিয়ন্ত্রণের বাইরে। এটা বিভিন্ন কারণে ঘটতে পারে। আপনি পরে পুনরায় চেষ্টা করলে হয়তো এটি কাজ করবে, আপনি এটিও পরীক্ষা করতে পারেন যে Google Play পরিষেবাটি সিস্টেম সেটিংসে ডেটা ব্যবহারের ক্ষেত্রে সীমাবদ্ধ নয়, অথবা আপনার ডিভাইসের ঘড়ি সঠিক, বা এটি কাস্টম রমতে ঘটতে পারে। + [%1$s] +\nএই ত্রুটিটি ${app_name} এর নিয়ন্ত্রণের বাইরে এবং Google এর মতে, এই ত্রুটিটি ইঙ্গিত করে যে ডিভাইসটিতে FCM এর সাথে নিবন্ধিত অনেকগুলি অ্যাপ্লিকেশন রয়েছে। ত্রুটিগুলি কেবলমাত্র অ্যাপ্লিকেশনের চরম সংখ্যাগুলিতে ঘটে থাকে, তাই এটি গড় ব্যবহারকারীকে প্রভাবিত করবে না। + FCM টোকেন উদ্ধার করতে ব্যর্থ হয়েছে: +\n%1$s + FCM টোকেন সফলভাবে উদ্ধার করা হয়েছে: +\n%1$s + Firebase এর টোকেন + Play Services ঠিক করুন + ${app_name} পুশ বার্তার প্রদানের জন্য Google Play পরিষেবাদি ব্যবহার করে কিন্তু এটি সঠিকভাবে কনফিগার করা বলে মনে হচ্ছে না: +\n%1$s + গুগল প্লে সার্ভিসেস APK পাওয়া গেছে এবং আপ টু ডেট রয়েছে। + Play Services পরীক্ষা + সেটিংস যাচাই করুন + কাস্টম নিয়ম লোড করতে ব্যর্থ হয়েছে, আবার চেষ্টা করুন। + কিছু বিজ্ঞপ্তি আপনার কাস্টম সেটিংস এ নিষ্ক্রিয় করা হয়েছে। + লক্ষ্য করুন যে কিছু বার্তা টাইপ নীরব করা হয়েছে (কোন শব্দ ছাড়াই একটি বিজ্ঞপ্তি তৈরি করবে)। + কাস্টম সেটিংস। + সক্ষম + বিজ্ঞপ্তি এই সেশানের জন্য অনুমতি দেওয়া হয় নি। +\n${app_name} এর সেটিংস যাচাই করুন। + বিজ্ঞপ্তি এই সেশানের জন্য সক্রিয় করা হয়েছে। + সেশান সেটিংস। + সক্ষম + বিজ্ঞপ্তি আপনার অ্যাকাউন্টের জন্য নিষ্ক্রিয় করা হয়েছে। +\nঅনুগ্রহ করে একাউন্ট সেটিংস যাচাই করে নিন। + বিজ্ঞাপ্তি আপনার একাউন্টএর জন্য সক্রিয় করা হোক. + অক্কোউন্টের সেটিংস। + সেটিংস খুলুন + বিজ্ঞাপ্তিকে সিস্টেমের সেটিংস এর মাধ্যমে নিষ্ক্রিয় করা হয়েছে। +\nদেয়া করে সিস্টেমের সেটিংসগুলি যাচাই করে নিন। + বিজ্ঞাপ্তিকে সিস্টেমের সেটিংস এর মাধ্যমে সক্রিয় করা হয়েছে। + সিস্টেমের সেটিংস। + এক অথবা অধিক পরীক্ষা ব্যর্থ হয়েছে,দয়া করে জমা করুন একটা গুরুত্বপূর্ণ খসড়া যেটা সাহায্য করবে অনুসন্ধান করতে। + এক অথবা অধিক পরীক্ষা বার্থ হয়েছে,প্রস্তাবিত ঠিক করে চেষ্টা করুন(es). + সাধারণ লক্ষণ ঠিক আছে.যদি আপনি এখনও কোনো প্রজ্ঞাপন পাননি,দয়াকরে জমা করুন একটা গুরুত্বপূর্ণ খসড়া যেটা সাহায্য করবে তদন্ত করতে। + (%1$d of %2$d) চলছে… + পরীক্ষাগুলি চালাও + ডায়াগনস্টিকস এর সমস্যা সমাধান + বিজ্ঞপ্তিগুলি সমাধান করুন + বিজ্ঞপ্তির গোপনীয়তা + ঘটনা দ্বারা বিজ্ঞপ্তির গুরুত্ব + উন্নত বিজ্ঞপ্তি সেটিংস + প্রমাণীকরণ প্রয়োজন + আপনার পাসওয়ার্ড নিশ্চিত করুন + সিস্টেম সেটিংস অ্যাপ্লিকেশন তথ্য প্রদর্শন করুন। + আবেদন তথ্য + যোগ করুন ফোন নম্বর + ফোন + যোগ করুন ইমেইল এর ঠিকানা + ইমেইল + প্রদশনীয় নাম + নথির ছবি + গোপনীয় পন্থা + গ্রন্থস্বত্ব + তৃতীয় পক্ষের বিগপ্তি + শর্তাবলী + সংস্করণ %s + সংস্করণ + সেটিংস + বার্তা + ঘরের পর্দার ছোটোখাটো যোগ করুন + ভুলেযাওয়া + ত্যাগ করুন কথোপকথন + সরাসরি চ্যাট + অনাগ্রাধিকার + প্রিয় + নিঃশব্দ + শুধুমাত্র উল্লেখ করুন + সব বার্তাগুলি + সব বার্তা (হইচই) + খোঁজা হচ্ছে নির্দেশনা… + + %২$s এর জন্য ঘর খোঁজা হয়েছে %১$s + %২$s এর জন্য ঘরগুলি খোঁজা হয়েছে %১$s + + + একটা ঘর + %d ঘরগুলি + + ব্রাউস নির্দেশনা + আদর্শ ঘরের পরিচয় অথবা একটা ঘর আর উপনাম + যোগকরুন একটা ঘর + যোগদান করুন ঘরে + ঘর তৈরিকরা + চ্যাট শুরু করুন + আমন্ত্রণ + কম গুরুত্ব + ঘরগুলি + প্রিয় + নির্দেশক + যোগদান + নথি + লোকজন + বার্তাগুলি + ঘর + কোনো ফলাফল নেই + ছাঁকুন ঘরের সংখ্যাগুলো + খোঁজা + নামানো বাতিল করুন + আপলোড বাতিল করুন + ব্যাবহারকারির কাছ থেকে আপনি কি সব বার্তা লোকাতে চান\?উল্লেখ্য এই প্রতিক্রিয়াটি চালু করবেন এবং এটি কিছু সময় নিতে পারে। + বিষয়বস্তুর বিবরণী দেয়ার জন কারণ + যোগ করেছেন + আমন্ত্রিত + বিকৃত পরিচয়।একটি ইমেইল ঠিকানা বা একটি মাধ্যমিক পরিচয় হতে হবে যেমন \'@localpart:domain\' + + %d নির্বাচিত + + + সেটিংস + নথিগুলি + লোকজন + ঘরের বিস্তারিত + কেবলমাত্র সার্ভার প্রশাসকটি উপরের মেলে এমন একটি আঙুলের ছাপ প্রকাশ করলেই শংসাপত্রটি গ্রহণ করবে। + "সার্টিফিকেটটি পূর্বে বিশ্বাসযোগ্য এক থেকে একে বিশ্বস্ত নয়.সার্ভারটি এটির শংসাপত্র পুনর্বিকরণ করেছে।অনুমোদিত আঙুলের ছাপ এর জন্য সার্ভার প্রশাসকের সাথে যোগাযোগ করুন।" + শংসাপত্র আপনার ফোন দ্বারা বিশ্বাস করা হয় যে এক থেকে পরিবর্তীত হয়েছে। এটি অত্যন্ত অস্বাভাবিক। আপনি এই নতুন শংসাপত্র গ্রহণ না করা বাঞ্চনীয়। + যদি সার্ভার এডমিনিস্ট্রেটর বলে থাকেন যে এটি প্রত্যাশিত,নিচের আঙুলের ছাপ প্রদত্ত আঙুলের ছাপ মেলে নিশ্চিত করুন। + "এর অর্থ এই যে কেউ দূষিতভাবে আপনার ট্রাফিক কে আটকাতে পারে অথবা আপনার ফোন রিমোট সার্ভার দ্বারা সর্বরাহিত শংসাপত্রের উপর বিশ্বাস করে না." + রিমোট সার্ভারএর পরিচয় যাচাই করা হয়নি। + আঙুলেরছাপ(%s): + উপেক্ষাকরা + লগআউট + বিশ্বাস করবেন না + আস্থাস্থাপন + + ১টি নতুন বার্তা + %d টি নতুন বার্তা + + আপনার এই রুমে পোস্ট করার অনুমতি নেই + ফাইল খুঁজে পাওয়া যায় নি + অপ্রত্যাশিত বার্তা মুছে দিন + অপ্রচলিত বার্তা আবার পাঠান + সব বাতিল করুন + সব আবার পাঠান + অজানা সেশান উপস্থিত থাকার কারণে বার্তা পাঠানো হয় নি। %1$s বা %2$s এখন\? + বার্তা পাঠানো হয় নি। %1$s বা %2$s এখন\? + সার্ভারের সাথে সংযুক্তি হারিয়ে গেছে। + একটি উত্তর পাঠান (এনক্রিপশনবিহীন)… + একটি এনক্রিপ্ট করা উত্তর পাঠান… + একটি বার্তা পাঠান (এনক্রিপশনবিহীন)… + একটি এনক্রিপ্ট করা বার্তা পাঠান… + %1$s আর %2$s এবং অন্যরা টাইপ করছেন… + %1$s আর %2$s টাইপ করছেন… + %s টাইপ করছেন… + অনুসন্ধান + ইমেইল বা ম্যাট্রিক্স আইডি + এক বা একাধিক ইমেইল ঠিকানা বা ম্যাট্রিক্স আইডি লিখুন + আইডি দ্বারা ব্যবহারকারী আমন্ত্রণ কর + শুধুমাত্র ম্যাট্রিক্স ব্যবহারকারীরা + ব্যবহারকারী নির্দেশিকা (%s) + স্থানীয় যোগাযোগ (%d) + আইডি দ্বারা আমন্ত্রিত + %1$s %2$s + %1$s আর %2$s + "%1$s, " + আপনি এই চ্যাটে %s কে আমন্ত্রণ জানাতে চান\? + কারণ + নিষিদ্ধ মুক্ত ব্যবহারকারীরা আবার ঘরে যোগদানের অনুমতি দেওয়া হবে। + আপনি একটি ছবি পাঠিয়েছেন। + নিষিদ্ধ ব্যবহারকারীরা তাদের এই ঘর থেকে কীক মেরে দেবে এবং আবার যোগদান করতে বাধা দেবেন। + ব্যবহারকারী কে নিষেধাজ্ঞা মুক্ত করুন + নিষিদ্ধ করার কারণ + ব্যবহারকারীকে নিষিদ্ধ করুন + কীক করা ব্যবহারকারী কে তাদের এই ঘর থেকে সরিয়ে দেবে। +\n +\nতাদের আবার যোগদানের হাত থেকে বাঁচাতে আপনার পরিবর্তে ওদেরকে নিষিদ্ধ করা উচিত। + কীক করার কারণ + ব্যবহারকারী কে কীক করুন + আপনি কি নিশ্চিত যে আপনি এই ব্যবহারকারীর জন্য আমন্ত্রণটি বাতিল করতে চান\? + আমন্ত্রণ বাতিল করুন + উপেক্ষা + এই ব্যবহারকারী কে উপেক্ষা তালিকা থেকে সরালে তাদের থেকে সমস্ত বার্তা আবার দেখাবে। + উপেক্ষা তালিকা থেকে ব্যবহারকারীকে সরান + উপেক্ষা + এই ব্যবহারকারীর উপেক্ষা করা আপনার ভাগ করা কক্ষগুলি থেকে তাদের বার্তা সরিয়ে দেবে। +\n +\nআপনি সাধারণ সেটিংসে যে কোনও সময় এই ক্রিয়াটি বিপরীত করতে পারেন। + ব্যবহারকারীকে উপেক্ষা করুন + হীনপদস্থ + আপনি নিজেকে হ্রাসকারী হিসাবে আপনি এই পরিবর্তনটিকে পূর্বাবস্থায় ফিরিয়ে আনতে পারবেন না, আপনি যদি রুমের সর্বশেষ সুবিধাযুক্ত ব্যবহারকারী হন তবে সুযোগ সুবিধাগুলি ফিরে পাওয়া অসম্ভব। + নিজেকে হিনপদস্থ করবেন\? + আপনি এই পরিবর্তনটি পূর্বাবস্থায় ফিরিয়ে আনতে সক্ষম হবেন না যেহেতু আপনি ব্যবহারকারীকে একই শক্তি স্তর হিসাবে প্রচার করার জন্য প্রচার করছেন। +\nআপনি কি নিশ্চিত\? + সেশান তালিকা প্রদর্শন কর + উল্লেখ + ব্যবহারকারী আইডি, নাম বা ইমেইল + অ্যাডমিন কর + মডারেটর কর + স্বাভাবিক ব্যবহারকারী তে রিসেট করুন + পদাঘাত + পাঠান যেমন + + ১ সদস্যাতা পরিবর্তন + %d সদস্যাতা পরিবর্তন + + গোষ্ঠীগুলি সারি + পড়ো প্রাপ্তিগুলি সারি + দয়া করে শুরু করুন ${app_name} অন্য সেশান যেটা পারে বর্ণনা করতে বার্তা কে সুতরাং এটা পারে পাঠাতে চাবিগুলো কে যন্ত্র তে. + অনুরোধ পাঠানো + চাবির অনুরোধ পাঠানো। + পুনরায় অনুরোধ এনক্রিপশন চাবিগুলিআপনার অন্য সেশানগুলি থেকে। + এই ইমেইল লিংক যেটা এখনো ক্লিক করা হয়নি + এই ব্যাবহারকারী আগেও নাম ব্যবহার করে ছিল + অনেকগুলো অনুরোধ পাঠানো হয়েছে + ধারণ করছে না সঠিক JSON + বিকৃত JSON + প্রবেশকারী টোকেনটা চিনতে নির্দিষ্ট ছিলো না + অবৈধ ব্যাবহারকারির নাম/সংকেত শব্দ + আপনার যন্ত্র একটি সেকেলে TLS নিরাপত্তা খসড়া চুক্তি,অরক্ষিত সংঘর্ষ করতে ,আপনার নিত্যাপাত্তার জন্য আপনি সম্পর্ক করতে পারবেন না + এসএসএল ত্রুটি। + এসএসএল ত্রুটি: পিয়ারের পরিচয় যাচাই করা হয়নি। + এই ইউআরএলে কোনও হোম সার্ভারে পৌঁছানো যায় না, দয়া করে এটি পরীক্ষা করে দেখুন + এটি কোনও বৈধ ম্যাট্রিক্স সার্ভারের ঠিকানা নয় + এই URL টি পৌঁছানোর যোগ্য নয়, দয়া করে এটি পরীক্ষা করুন + একটি বৈধ URL প্রবেশ করুন + নিবন্ধন করতে অক্ষম: ইমেইল মালিকানা ব্যর্থতা + নিবন্ধন করতে অক্ষম + নিবন্ধন করতে অক্ষম: নেটওয়ার্ক ত্রুটি + লগইন করতে অক্ষম + লগ ইন করতে ব্যর্থ: নেটওয়ার্ক ত্রুটি + URL টি http[s]:// দিয়ে শুরু করতে হবে + পর্যালোচনা করুন এবং এই হোমসার্ভার এর নীতিগুলি গ্রহণ করুন: + আপনার পাসওয়ার্ড পুনরায় সেট করা হয়েছে। +\n +\nআপনি সমস্ত সেশান থেকে লগ আউট করা হয়েছে এবং আর পুশ বিজ্ঞপ্তি পাবেন না। বিজ্ঞপ্তিগুলি পুনরায় সক্ষম করতে, প্রতিটি ডিভাইসে পুনরায় লগ ইন করুন। + ইমেল ঠিকানা যাচাই করতে ব্যর্থ: ইমেলটিতে লিঙ্কে আপনি ক্লিক করেছেন তা নিশ্চিত করুন + একটি ইমেল %s কে পাঠানো হয়েছে। একবার এটিতে থাকা লিঙ্কটি অনুসরণ করার পরে, নীচে ক্লিক করুন। + একটি নতুন পাসওয়ার্ড প্রবেশ করা আবশ্যক। + আপনার অ্যাকাউন্টের সাথে যুক্ত ইমেল ঠিকানা প্রবেশ করা আবশ্যক। + আপনার পাসওয়ার্ড পুনরায় সেট করতে, আপনার অ্যাকাউন্টের সাথে যুক্ত ইমেল ঠিকানাটি প্রবেশ করান: + আমি আমার ইমেইল ঠিকানা যাচাই করেছি + পরিচয় সার্ভার: + হোম সার্ভার: + ব্যবহারকারীর নাম ব্যবহার করা + এই হোম সার্ভার আপনি একটি রোবট না সেটা নিশ্চিত করবে + ইমেইল এবং ফোন নম্বরের সাথে একযোগে নিবন্ধীকরণ এখনও সমর্থিত নয় যতক্ষন না API উপলব্ধ হয়ে। শুধুমাত্র ফোন নম্বর অ্যাকাউন্ট গ্রহণ করা হবে। +\n +\nআপনি সেটটিংসে গিয়ে আপনার প্রোফাইলে আপনার ইমেল যোগ করতে পারেন। + নিবন্ধন চালিয়ে যেতে আপনার ইমেইল চেক করুন + কাস্টম সার্ভার বিকল্প ব্যবহার করুন (উন্নত) + পাসওয়ার্ড ভুলে গেছেন\? + পাসওয়ার্ডগুলি মিলছে না + অবৈধ টোকেন + অনুপস্থিত ইমেইল ঠিকানা বা ফোন নম্বর + অনুপস্থিত ফোন নম্বর + অনুপস্থিত ইমেইল ঠিকানা + এই ইমেইল ঠিকানা ইতিমধ্যে সংজ্ঞায়িত করা হয়। + এটি একটি বৈধ ফোন নম্বরের মতো দেখাচ্ছে না + এটি একটি বৈধ ইমেল ঠিকানা মত দেখাচ্ছে না + নিখোঁজ পাসওয়ার্ড + পাসওয়ার্ড খুব ছোট (সর্বনিম্ন ৬) + ব্যবহারকারীর নামগুলিতে শুধুমাত্র অক্ষর, সংখ্যা, বিন্দু, হাইফেন এবং আন্ডারস্কোর থাকতে পারে + ভুল ব্যবহারকারীর নাম এবং / অথবা পাসওয়ার্ড + আপনার নতুন পাসওয়ার্ড নিশ্চিত করুন + পুনরাবৃত্তি পাসওয়ার্ড + ফোন নাম্বার (ঐচ্ছিক) + ফোন নম্বর + ইমেইল ঠিকানা (ঐচ্ছিক) + ইমেইল ঠিকানা + অ্যাকাউন্ট পুনরুদ্ধারের জন্য একটি ইমেল সেট করুন। আপনার পরিচিত লোকদের দ্বারা বিকল্প হিসাবে আবিষ্কারের জন্য পরে ইমেল বা ফোন ব্যবহার করুন। + অ্যাকাউন্ট পুনরুদ্ধারের জন্য একটি ইমেল সেট করুন। আপনার পরিচিত লোকদের দ্বারা বিকল্প হিসাবে আবিষ্কারের জন্য পরে ইমেল বা ফোন ব্যবহার করুন। + একটি ফোন সেট করুন এবং পরে আপনাকে জারা চেনে সেই লোকেদের দ্বারা বিকল্প হিসাবে আবিষ্কার করার জন্য। + অ্যাকাউন্ট পুনরুদ্ধারের জন্য একটি ইমেল সেট করুন এবং পরে আপনাকে চিনে এমন লোকেরা ইচ্ছিকভাবে আবিষ্কারযোগ্য। + ব্যবহারকারীর নাম + নতুন পাসওয়ার্ড + পাসওয়ার্ড + ইমেল বা ব্যবহারকারীর নাম + লগইন স্ক্রিনে ফায়ার আসুন + রিসেট ইমেইল পাঠান + বাদ + জমা + অ্যাকাউন্ট তৈরি করুন + একক সাইন অন দিয়ে সাইন ইন করুন + লগ ইন + দুঃখিত, এই কর্মটি সম্পন্ন করার জন্য কোন বাহ্যিক অ্যাপ্লিকেশন খুঁজে পাওয়া যায়নি। + সঙ্গে যান… + আপনার বর্তমানে সক্রিয় কোন স্টিকার প্যাক নেই। +\n +\nএখন কিছু যোগ করবেন\? + ভিডিও নিন + ছবি তোল + ছবি বা ভিডিও নিন + স্টিকার পাঠাও + ফাইলগুলো পাঠাও + এইচডি চালু করুন + এইচডি বন্ধ করুন + পেছনের + সম্মুখ + ক্যামেরা স্যুইচ করুন + বেতার হেডসেট + হেডসেট + স্পিকার + ফোন + সাউন্ড ডিভাইস নির্বাচন করুন + রিয়েল টাইম সংযোগ স্থাপন করতে ব্যর্থ। +\nকলগুলি নির্ভরযোগ্যতার সাথে কাজ করার জন্য দয়া করে আপনার হোমসার্ভারের প্রশাসককে একটি টার্ন সার্ভার কনফিগার করতে বলুন। + এলিমেন্ট কল ব্যর্থ + আবার আমাকে জিজ্ঞাসা করবেন না + %s ব্যবহার করার চেষ্টা করুন + কলগুলি নির্ভরযোগ্যভাবে কাজ করার জন্য দয়া করে আপনার হোমসার্ভার (%1$s) এর প্রশাসককে একটি টার্ন সার্ভার কনফিগার করতে বলুন। +\n +\nবিকল্পভাবে, আপনি পাবলিক সার্ভারটি %2$s এ ব্যবহার করার চেষ্টা করতে পারেন, তবে এটি নির্ভরযোগ্য হবে না এবং এটি আপনার সার্ভারের সাথে আপনার আইপি ঠিকানাটি ভাগ করে দেবে। আপনি সেটিংসে এটি পরিচালনা করতে পারেন। + ভুল কনফিগার্ড সার্ভারের কারণে কল ব্যর্থ হয়েছে + আপনি কি একটি ভিডিও কল শুরু করতে চাওয়ার বিষয়ে নিশ্চিত\? + আপনি কি একটি ভয়েস কল শুরু করতে চাওয়ার বিষয়ে নিশ্চিত\? + আপনি কি %s এর সাথে একটি নতুন চ্যাট শুরু করতে চান\? + ভয়েস পাঠান + ভিডিও কল শুরু করুন + ভয়েস কল শুরু করুন + নতুন চ্যাট শুরু করুন + অনুসন্ধান + আইডেন্টিটি সার্ভার ইউআরএল + হোম সার্ভার ইউআরএল + সাইন আউট + লগ ইন + অ্যাকাউন্ট তৈরি করুন + ব্যবহারকারীর নাম + রুমে যোগদান + অধীত + মধ্যে পাঠান + উন্নতি (%s%%) + বাগ রিপোর্ট পাঠানো ব্যর্থ হয়েছে (%s) + বাগ রিপোর্ট সফলভাবে পাঠানো হয়েছে + বাগ রিপোর্ট করার জন্য জোরে ঝাঁকান + অ্যাপ্লিকেশন শেষ সময় ক্র্যাশ হয়েছে। আপনি ক্র্যাশ রিপোর্ট স্ক্রিনটি খুলতে চান\? + আপনি হতাশায় ফোন কম্পন করা বলে মনে হচ্ছে। আপনি বাগ রিপোর্ট স্ক্রিন খুলতে চান\? + সমস্যাগুলির নির্ণয়ের জন্য, এই ক্লায়েন্ট থেকে লগগুলি এই বাগ রিপোর্টটি পাঠানো হবে। লগ এবং স্ক্রিনশট সহ এই বাগ রিপোর্টটি সর্বজনীনভাবে দৃশ্যমান হবে না। আপনি যদি শুধুমাত্র উপরের পাঠ্যটি পাঠাতে চান তবে অনুগ্রহ করে আনটিক করুন: + এখানে আপনার সমস্যা বর্ণনা করুন + যদি সম্ভব হয়, ইংরেজি বিবরণ লিখুন। + বাগ বর্ণনা করুন। আপনি কি করেছিলেন\? আপনি কি ঘটতে আশা করেন\? আসলে কি ঘটেছে\? + বাগ রিপোর্ট + স্ক্রিনশট পাঠান + ক্র্যাশ লগগুলি পাঠান + লগগুলি পাঠান + কোন গ্রুপ নেই + সম্প্রদায়গুলি + আমন্ত্রণ + + ১ জন ব্যবহারকারী + %d জন ব্যবহারকারী + + কোন পাবলিক রুম উপলব্ধ নেই + কোন রুম নেই + রুমের ডিরেক্টরি + রুমগুলি + কোনও পরিচয় সার্ভার কনফিগার করা নেই। + কোন ফলাফল নেই + আপনি রায়টকে আপনার স্থানীয় পরিচিতি অ্যাক্সেস করার অনুমতি দেয় নি + কথাবার্তা নেই + শুধুমাত্র ম্যাট্রিক্সের যোগাযোগগুলি + ব্যবহারকারী ডিরেক্টরি + স্থানীয় ঠিকানার বই + কথাবার্তাগুলি + সিস্টেম সতর্কতাগুলি + কম অগ্রাধিকার + আমন্ত্রণগুলি + কমিউনিটির নামগুলি ফিল্টার কর + রুমের নামগুলি ফিল্টার কর + বেক্তিগুলি ফিল্টার কর + ফেভারিটগুলি ফিল্টার কর + রুমের নাম ফিল্টার কর + সম্প্রদায়গুলি + রুম + জনসাধারণ + প্রিয় + বিজ্ঞপ্তিগুলি + হোম + সাফল্য + ত্রুটি + সতর্কতা + প্রতিপাদন + অক্ষম + ক্লিপবোর্ডে অনুলিপি করা হয়েছে + অনুলিপি + বন্ধ + খুলুন + পঠিত হিসেবে চিহ্নিত + দ্রুত উত্তর + ঐতিহাসিক + সবগুলো পঠিত বলে সনাক্ত কর + গ্লোবাল অনুসন্ধান + ভিডিও কল + ভয়েস কল + আপনি সাইন আউট করতে চান\? + সিগণ আউট + ক্রিয়াকলাপ + প্রস্থান + বন্ধ করুন + পতন + গ্রহণ + পতন + পর্যালোচনা + উপেক্ষা + বাতিল + সম্পন্ন + \'%s\' একটি উপনাম জন্য বৈধ বিন্যাস নয় + অবৈধ উদীয়মান বিন্যাস + \'%s\' একটি বৈধ সম্প্রদায় আইডি নয় + অবৈধ কমিউনিটি আইডি + নতুন সম্প্রদায় আইডি (উদাঃ +foo:matrix.org) + এই রুম কোন সম্প্রদায়ের জন্য ফ্লেয়ার দেখাচ্ছে না + নতুন ঠিকানা (যেমন #foo:matrix.org) + এই রুমে কোন স্থানীয় ঠিকানা নেই + এই সেশান থেকে এই রুমে যাচাই করা সেশানগুলিতে এনক্রিপ্ট করা বার্তাগুলি কখনও প্রেরণ করবেন না। + শুধুমাত্র যাচাই সেশানে এনক্রিপ্ট করুন + এনক্রিপশন সক্রিয় করতে সক্ষম হবার জন্য আপনাকে লগআউট করতে হবে। + শেষ থেকে শেষ এনক্রিপশন সক্রিয় আছে + শেষ থেকে শেষ এনক্রিপশন + এই অপ্রত্যাশিত উপায়ে বিরতি পারে যে পরীক্ষামূলক বৈশিষ্ট্য। সতর্কতার সাথে ব্যবহার করুন। + ল্যাবস + ঠিকানা + এই রুম এর অভ্যন্তরীণ আইডি + উন্নত + + %d নিষিদ্ধ ব্যবহারকারী + %d নিষিদ্ধ ব্যবহারকারী + + নিষিদ্ধ ব্যবহারকারীরা + রুম এর লিঙ্ক জানেন যে কেউ, অতিথি সহ + রুমের লিঙ্কটি যে কেউ জানে, অতিথিদের ছাড়া + শুধুমাত্র যারা আমন্ত্রিত হয়েছে + একটি রুম লিঙ্ক করার জন্য এটি একটি ঠিকানা থাকতে হবে। + সদস্য শুধুমাত্র (তারা যোগদান করে) + সদস্য শুধুমাত্র (তারা আমন্ত্রিত ছিল) + সদস্য শুধুমাত্র (এই বিকল্পটি নির্বাচন করার সময় থেকে) + যে কেউ + কে এই রুম অ্যাক্সেস করতে পারেন\? + কে ইতিহাস পড়তে পারে\? + কক্ষ ইতিহাস পাঠযোগ্যতা + রুম অ্যাক্সেস + বিজ্ঞপ্তিগুলি + রুমের তালিকা তে এই রুম টি যোগ করুন + অ্যাক্সেস এবং দৃশ্যমানতা + কেউ না + কম অগ্রাধিকার + প্রিয় + হিসেবে ট্যাগ করা: + রুমের ট্যাগ + বিষয় + রুমের নাম + রুমের ছবি + চিরতরে + ১ মাস + ১ সপ্তা + ৩ দিন + আপনি বর্তমানে কোন সম্প্রদায়ের সদস্য নন। + ফ্লেয়ার + শাটার শব্দ চালান + চয়ন + ডিফল্ট মিডিয়া উৎস + চয়ন + ডিফল্ট কম্প্রেশন + মিডিয়া + "অতিরিক্ত তথ্য: %s" + আপনার ফোন নম্বর যাচাই করার সময় একটি ত্রুটি ঘটেছে। + কোড + আপনার ফোন নম্বর যাচাই করার সময় ত্রুটি হয়েছে + অ্যাক্টিভেশন কোড প্রবেশ করান + আমরা একটি অ্যাক্টিভেশন কোড দিয়ে একটি এসএমএস পাঠিয়েছি। নীচের এই কোড লিখুন দয়া করে। + ফোন যাচাইকরণ + নির্বাচিত দেশের জন্য অবৈধ ফোন নম্বর + ফোন নম্বর + একটি দেশ নির্বাচন করুন + দেশ + দেশ বেঁচে নিন + আপনি কি %1$s %2$s সরাতে নিশ্চিত\? + আপনি এই বিজ্ঞপ্তি লক্ষ্য অপসারণ করতে চান আপনি কি নিশ্চিত\? + পাসওয়ার্ড মিলছে না + %s থেকে সব বার্তা দেখতে চান\? +\n +\nমনে রাখবেন যে এই পদক্ষেপটি অ্যাপ্লিকেশনটি পুনরায় চালু করবে এবং এটি কিছু সময় নিতে পারে। + আপনার পাসওয়ার্ড আপডেট করা হয়েছে + পাসওয়ার্ড বৈধ নয় + পাসওয়ার্ড আপডেট করতে ব্যর্থ হয়েছে + পাসওয়ার্ড আপডেট করুন + নিশ্চিত কর নতুন পাসওয়ার্ড + নতুন পাসওয়ার্ড + বর্তমান পাসওয়ার্ড + পাসওয়ার্ড পরিবর্তন করুন + পাসওয়ার্ড + আপনার ইমেল ঠিকানা যাচাই করার সময় একটি ত্রুটি ঘটেছে। + এই ফোন নম্বর ইতিমধ্যে ব্যবহার করা আছে। + এই ইমেইল ঠিকানা পাওয়া যায় নি। + এই ইমেইলটা ইতোমধ্যে ব্যবহৃত হচ্ছে। + ইমেইল ঠিকানা যাচাই করতে অক্ষম। আপনার ইমেইল চেক করুন এবং লিঙ্কে ক্লিক করুন। একবার সম্পন্ন হলে, অবিরত ক্লিক করুন। + আপনার ইমেইল চেক করুন এবং লিঙ্কে ক্লিক করুন। একবার সম্পন্ন হলে, অবিরত ক্লিক করুন। + যাচাই মুলতুবি + ভাষা বেছে নিন + ভাষা + ব্যবহারকারী ইন্টারফেস + এটি করতে সেটিংসে \'একীকরণের অনুমতি দিন\' সক্ষম করুন। + সংহতকরণ অক্ষম করা হয়েছে + ইন্টিগ্রেশন ম্যানেজার + সংহতকরণের অনুমতি দিন + পরিচয় সার্ভার + হোম সার্ভার + লগ ইন করুন + জমা দিন + পাসওয়ার্ড: + প্রমাণীকরণ + এই অপারেশন অতিরিক্ত প্রমাণীকরণ প্রয়োজন। +\nচালিয়ে যেতে, আপনার পাসওয়ার্ড লিখুন। + %1$s @ %2$s + শেষ দেখা + সর্বজনীন নাম আপডেট করুন + সর্বজনীন নাম + আইডি + সেশানের তথ্য + ডেটা সংরক্ষণ মোড একটি নির্দিষ্ট ফিল্টার প্রয়োগ করে যাতে উপস্থিতি আপডেট এবং টাইপিং বিজ্ঞপ্তি ফিল্টার করা হয়। + ডেটা সংরক্ষণ মোড + হ্যাঁ, আমি সাহায্য করতে চাই! + আমাদের ${app_name} উন্নত করতে সাহায্য করার জন্য বিশ্লেষণ সক্রিয় করুন। + ${app_name} আমাদের অ্যাপ্লিকেশন উন্নত করার অনুমতি দেওয়ার জন্য বেনামী বিশ্লেষণ সংগ্রহ করে। + বিশ্লেষণ তথ্য পাঠান + বৈশ্লেষিক ন্যায় + অনুমতি প্রদান করুন + নির্ভরযোগ্য বিজ্ঞপ্তি পেতে ${app_name} কম প্রভাব ব্যাকগ্রাউন্ড সংযোগ রাখা প্রয়োজন। +\nপরবর্তী স্ক্রিনে আপনাকে দাঙ্গাটি সর্বদা ব্যাকগ্রাউন্ডে চালানোর অনুমতি দেওয়া হবে, দয়া করে স্বীকার করুন। + পটভূমি সংযোগ + অন্য বিকল্প চয়ন করুন + অনুমতি প্রদান করুন + সুরক্ষিতভাবে এবং ব্যক্তিগতভাবে আপনার বিজ্ঞপ্তি পরিচালনা করতে ${app_name} পটভূমিতে চালাতে পারে। এই ব্যাটারি ব্যবহার প্রভাবিত হতে পারে। + বিজ্ঞপ্তি\'র গোপনীয়তা + আপনার আবিষ্কারের সেটিংস পরিচালনা করুন। + আবিষ্কার + আমার একাউন্ট নিষ্ক্রিয় করুন + একাউন্ট নিষ্ক্রিয় + এটি আপনার বর্তমান কী বা বাক্যাংশটি প্রতিস্থাপন করবে। + আপনার বিদ্যমান ব্যাকআপের জন্য একটি নতুন সুরক্ষা কী তৈরি করুন বা একটি নতুন সুরক্ষা বাক্য সেট করুন। + আপনার সার্ভারে এনক্রিপশন কীগুলি ব্যাক আপ করে এনক্রিপ্ট করা বার্তাগুলি এবং ডেটাতে অ্যাক্সেস হারানোর বিরুদ্ধে সুরক্ষা। + এই ডিভাইসে সেট আপ করুন + সুরক্ষিত ব্যাকআপ পুনরায় সেট করুন + সুরক্ষিত ব্যাকআপ সেট আপ করুন + পরিচালনা + সুরক্ষিত ব্যাকআপ + নরম কীবোর্ডের প্রবেশ বোতামটি লাইন বিরতি যোগ করার পরিবর্তে বার্তা পাঠাবে + এন্টার বোতাম টিপে বার্তা পাঠান + পাঠানোর আগে মিডিয়া প্রিভিউ কর + একটি ব্যবহারকারী উল্লেখ এর সময় কম্পন করুন + অবতার এবং প্রদর্শন নাম পরিবর্তন অন্তর্ভুক্ত। + অ্যাকাউন্ট ইভেন্ট দেখান + আমন্ত্রণ, kicks, এবং নিষেধাজ্ঞা অনিবন্ধিত হয়। + যোগ এবং ছাড়া তথ্য দেখান + একটি বিস্তারিত তালিকা জন্য পঠন প্রাপ্তি ক্লিক করুন। + পাঠানো প্রাপ্তি দেখান + ১২-ঘন্টা ফরম্যাটে টাইমস্ট্যাম্প দেখান + সব বার্তা জন্য টাইমস্ট্যাম্প প্রদর্শন করুন + প্রেরিত হওয়ার আগে মার্কডাউন সিনট্যাক্স ব্যবহার করে বার্তাগুলি ফরম্যাট করুন। এটি ইটালিক পাঠ্য প্রদর্শনের জন্য তারকাচিহ্নগুলি ব্যবহার করার মতো উন্নত ফর্ম্যাটিংয়ের জন্য অনুমতি দেয়। + মার্কডাউন বিন্যাস + অন্যান্য ব্যবহারকারীদের কে আপনি টাইপ করছেন সেটা জানান। + টাইপিং বিজ্ঞপ্তি পাঠান + বার্তাগুলি মধ্যে থাকা লিংকগুলি প্রিভিউ করে যখন আপনার হোম সার্ভার এই বৈশিষ্ট্য টি সাপোর্ট করে। + ইনলাইন URL পূর্বরূপ + সেশানগুলি + অপঠিত বার্তা সঙ্গে পিন কক্ষগুলি + মিস বিজ্ঞপ্তি সঙ্গে পিন রুমগুলি + হোম প্রদর্শন + ফোনবুকের দেশ + পরিচিতিগুলির অনুমতি + স্থানীয় যোগাযোগগুলি + বিজ্ঞপ্তি লক্ষ্যমাত্রা + ক্র্রিপ্টোগ্রাফি কুঞ্জি ব্যবস্থাপনা + ক্রিপ্টোগ্রাফি + বট, সেতু, উইজেট এবং স্টিকার প্যাকগুলি পরিচালনা করতে ইন্টিগ্রেশন ম্যানেজার ব্যবহার করুন। +\nইন্টিগ্রেশন ম্যানেজাররা কনফিগারেশন ডেটা গ্রহণ করে এবং উইজেটগুলি সংশোধন করতে, রুম আমন্ত্রন প্রেরণ করতে এবং আপনার পক্ষে পাওয়ার স্তর নির্ধারণ করতে পারে। + ঐক্যবদ্ধতা + উন্নত + অন্যান্য + উপেক্ষিত ব্যবহারকারীদের + বিজ্ঞপ্তিগুলি + ব্যবহারকারী সেটিংস + মিডিয়া ক্যাশে পরিষ্কার করুন + ক্যাশে পরিষ্কার করুন + মিডিয়া রাখুন + গোপনীয়তা নীতি + কপিরাইট + তৃতীয় পক্ষের নোটিশ + শর্তাবলী + olm সংস্করণ + সংস্করণ + প্রতিটি সিঙ্কের মধ্যে বিলম্ব + %s +\nসিঙ্কটি ডিভাইসের সংস্থান (ব্যাটারি) বা অবস্থার (ঘুম) উপর নির্ভর করে পিছিয়ে যেতে পারে। + পছন্দের সিঙ্ক ব্যবধান + সিঙ্ক অনুরোধ সময়সীমার + ব্যাকগ্রাউন্ড সিঙ্ক সক্ষম করুন + বুট করার সময় শুরু + সেটিংস আপডেট করতে ব্যর্থ। + অ্যাপটি ব্যাকগ্রাউন্ডে থাকা অবস্থায় আপনাকে আগত বার্তাগুলি সম্পর্কে অবহিত করা হবে না। + কোনও পটভূমি সিঙ্ক না + রায়ট নির্দিষ্ট সময়ে সময়ে পটভূমিতে সিঙ্ক হবে (কনফিগারযোগ্য)। +\nএটি রেডিও এবং ব্যাটারির ব্যবহারকে প্রভাবিত করবে, রায়ট ইভেন্টগুলি শুনছে বলে জানিয়ে একটি স্থায়ী বিজ্ঞপ্তি প্রদর্শিত হবে। + রিয়েল টাইম জন্য অনুকূলিত + রায়ট এমনভাবে পটভূমিতে সিঙ্ক হবে যা ডিভাইসের সীমিত সংস্থান (ব্যাটারি) সংরক্ষণ করে। +\nআপনার ডিভাইস রিসোর্স স্থিতির উপর নির্ভর করে সিঙ্কটি অপারেটিং সিস্টেম দ্বারা পিছিয়ে যেতে পারে। + ব্যাটারির জন্য অনুকূলিত + বাদ + স্বীকার + অফলাইন + আমন্ত্রণ + বা + যেকোন ভাবেই পাঠাও + যেকোন ভাবেই কল করুন + কনফারেন্স কল এনক্রিপ্ট কক্ষগুলিতে সমর্থিত নয় + সেশানের তথ্য + কল শুরু করা গেলো না + আপনি এই রুমে একটি সম্মেলন শুরু করার জন্য আমন্ত্রণ করার অনুমতি প্রয়োজন + অনুপস্থিত অনুমতিগুলির কারণে, এই পদক্ষেপটি সম্ভব নয়। + অনুপস্থিত অনুমতিগুলির কারণে, কিছু বৈশিষ্ট্য অনুপস্থিত হতে পারে… + কল শুরু করা যাচ্ছে না, পরে চেষ্টা করুন + ভিডিও + ধ্বনি + চলমান সম্মেলন কল। +\n %1$s বা %2$s হিসাবলে জুড়ুন + বাতিল + থামাও + চালু + সক্রিয় কল + কন্টেন্ট রিপোর্ট করুন + বিযুক্ত + রদ কর + কোনটা না + পুনঃনামকরণ + মুছুন + ডিক্রিপ্টেড সোর্স দেখুন + সোর্স দেখুন + পার্মালিঙ্ক + ফরওয়ার্ড + পরে + পরিষ্কার + বলা + ভাগ + ডাউনলোড + উদ্ধৃতি + অপসারণ + আবার পাঠান + পাঠান + থাক + ত্যাগ + সংরক্ষিত + বাতিল + ঠিক + লোড হচ্ছে… + তৃতীয় পক্ষের লাইসেন্সগুলি + আপনি সাইন আউট করার আগে আপনার কী ব্যাক আপ না হওয়া পর্যন্ত আপনার এনক্রিপ্ট হওয়া বার্তাগুলিতে অ্যাক্সেস হারাবেন। + ব্যাকআপ + আপনি কি নিশ্চিত\? + কী ব্যাকআপ ব্যবহার করুন + কী ব্যাকআপ করছে… + আমি আমার এনক্রিপ্টেড বার্তা চাই না + আপনার এনক্রিপ্ট করা বার্তাগুলির অ্যাক্সেস হারাতে এড়াতে আপনার সমস্ত সেশানগুলিতে নিরাপদ কী ব্যাকআপ সক্রিয় থাকা উচিত। + অগ্রগতি কী ব্যাকআপ। আপনি এখন সাইন আউট করলে আপনি আপনার এনক্রিপ্ট করা বার্তাগুলির অ্যাক্সেস হারাবেন। + আপনি এখন সাইন আউট করলে আপনার এনক্রিপ্ট হওয়া বার্তাগুলি হারিয়ে ফেলবেন + কী ব্যাকআপ শেষ হয়নি, দয়া করে অপেক্ষা করুন … + সেশন যাচাই করুন + কী ব্যাকআপ ব্যবহার করুন + কী ব্যাকআপ + একটি স্টিকার পাঠান + সম্প্রদায়ের বিবরণ + বাগ রিপোর্ট + ঐতিহাসিক + সদস্যের বিবরণ + সেটিংস + ঘর + বার্তাগুলি + নীরব বিজ্ঞপ্তিগুলি + সশব্দ বিজ্ঞপ্তিগুলি + ইভেন্টের জন্য শোনা হচ্ছে + সিংক্রোনাইজ হচ্ছে… + সেবা আরম্ভ করা হচ্ছে + কালো থিম + গাঢ় থিম + হালকা থিম + আপনি শেষ-থেকে-শেষ এনক্রিপশন চালু করেছেন (অজানা অ্যালগরিদম %1$s )। + %1$s এন্ড-টু-এন্ড এনক্রিপশন চালু করেছে (অজানা অ্যালগরিদম %2$s)। + আপনি শেষ থেকে শেষ এনক্রিপশন চালু করেছেন। + %1$s এন্ড-টু-এন্ড এনক্রিপশন চালু করেছে। + আপনি অতিথিদের ঘরে যোগদান করতে বাধা দিয়েছেন। + %1$s অতিথিদের ঘরে যোগদান করতে বাধা দিয়েছে। + আপনি অতিথিদের ঘরে যোগদানের অনুমতি দিয়েছেন। + %1$s অতিথিদের ঘরে যোগদানের অনুমতি দিয়েছে। + আপনি এই ঘরের মূল ঠিকানা সরিয়েছেন। + %1$s এই ঘরের মূল ঠিকানা সরিয়ে নিয়েছে। + আপনি এই ঘরের মূল ঠিকানাটি %1$s তে সেট করেছেন। + %1$s এই ঘরের মূল ঠিকানাটি %2$s তে সেট করে। + আপনি %1$s যোগ করেছেন এবং %2$s কে এই ঘরের ঠিকানা হিসাবে সরিয়ে দিয়েছেন। + %1$s %2$s যোগ করেছে এবং %3$s গুলি এই ঘরের ঠিকানা হিসাবে সরানো হয়েছে। + + আপনি এই ঘরের ঠিকানা হিসাবে %1$s সরিয়েছেন। + আপনি এই ঘরের ঠিকানা হিসাবে %1$s গুলি সরিয়েছেন। + + + %1$s এই ঘরের ঠিকানা হিসাবে %2$s সরানো হয়েছে। + %1$s %3$s কে এই ঘরের ঠিকানা হিসাবে সরানো হয়েছে। + + + আপনি এই কক্ষের জন্য ঠিকানা হিসাবে %1$s যুক্ত করেছেন। + আপনি এই কক্ষের ঠিকানা হিসাবে %1$s যুক্ত করেছেন। + + + %1$s এই ঘরের ঠিকানা হিসাবে %2$s যুক্ত করেছে। + %1$s এই ঘরের ঠিকানাগুলি হিসাবে %2$s যুক্ত করেছে। + + আপনি %1$s এর আমন্ত্রণ প্রত্যাহার করেছেন। কারণ: %2$s + %1$s %2$s এর আমন্ত্রণ ফেরত নিয়েছে। কারণ: %3$s + আপনি %1$s এর জন্য আমন্ত্রণটি গ্রহণ করেছেন। কারণ: %2$s + %1$s %2$s এর জন্য আমন্ত্রণ গ্রহণ করেছেন। কারণ: %3$s + আপনি %1$s এর কক্ষে যোগদানের জন্য আমন্ত্রণটি বাতিল করেছেন। কারণ: %2$s + %1$s %2$s এর কক্ষে যোগদানের আমন্ত্রণ বাতিল করে দিয়েছিল। কারণ: %3$s + আপনি %1$s কে ঘরে যোগদানের জন্য একটি আমন্ত্রণ প্রেরণ করেছেন। কারণ: %2$s + %1$s রুমের সাথে যোগ দিতে %2$s কে একটি আমন্ত্রণ পাঠিয়েছেন। কারণ: %3$s + আপনি %1$s কে নিষিদ্ধ করেছেন। কারণ: %2$s + %1$s %2$s কে নিষিদ্ধ করেছে। কারণ: %3$s + আপনি %1$s কে নিষিদ্ধ মুক্ত করেছেন। কারণ: %2$s + %1$s %2$s কে নিষিদ্ধ তালিকা থেকে মুক্ত করেছে। কারণ: %3$s + আপনি %1$s কে কীক করেছেন। কারণ: %2$s + %1$s %2$s কে কিক করেছে। কারণ: %3$s + আপনি আমন্ত্রণটি বাতিল করেছেন। কারণ: %1$s + %1$s আমন্ত্রণ বাতিল করেছেন। কারণ: %2$s + আপনি কক্ষ ছেড়ে দিয়েছেন। কারণ: %1$s + %1$s রুম ছেড়ে দিয়েছে। কারণ: %2$s + আপনি কক্ষে যোগ দিয়েছেন। কারণ: %1$s + %1$s রুম এ যোগ দিয়েছে। কারণ: %2$s + %1$s আপনাকে আমন্ত্রণ করেছে। কারণ: %2$s + আপনি %1$s কে আমন্ত্রিত করেছেন। কারণ: %2$s + %1$s আমন্ত্রিত করেছেন %2$s কে। কারণ: %3$s + আপনার আমন্ত্রণ। কারণ: %1$s + %1$s এর আমন্ত্রণ। কারণ: %2$s + প্রেরণ সারি পরিষ্কার করুন + বার্তা প্রেরণ করা হচ্ছে … + প্রাথমিক সিঙ্ক: +\nঅ্যাকাউন্ট ডেটা আমদানি করা হচ্ছে + প্রাথমিক সিঙ্ক: +\nসম্প্রদায়গুলি আমদানি করা হচ্ছে + প্রাথমিক সিঙ্ক: +\nছেড়ে দেওয়া কক্ষগুলিতে আমদানি করা হিচ্ছে + প্রাথমিক সিঙ্ক: +\nআমন্ত্রিত করা কক্ষগুলিতে আমদানি করা হিচ্ছে + প্রাথমিক সিঙ্ক: +\nযোগ করা কক্ষগুলিতে আমদানি করা হিচ্ছে + প্রাথমিক সিঙ্ক: +\nকক্ষগুলি আমদানি করা হচ্ছে + প্রাথমিক সিঙ্ক: +\nক্রিপ্টো আমদানি হচ্ছে + প্রাথমিক সিঙ্ক: +\nঅ্যাকাউন্ট আমদানি করা হচ্ছে… + খালি কক্ষ + + %1$s এবং অন্য ১ জন + %1$s এবং অন্যান্য %2$d জন + + %1$s এবং %2$s + কক্ষ আমন্ত্রণ + %s থেকে আমন্ত্রণ করুন + ফোন নম্বর + ইমেল ঠিকানা + খালি কক্ষে পুনরায় যোগদান করা বর্তমানে সম্ভব নয়। + ম্যাট্রিক্স ত্রুটি + নেটওয়ার্ক ত্রুটি + চিত্র আপলোড করতে ব্যর্থ + বার্তা পাঠাতে অক্ষম + পুনরায় প্রতিক্রিয়া করতে পারেনি + প্রেরকের ডিভাইস আমাদের এই বার্তার জন্য কীগুলি প্রেরণ করেনি। + ** ডিক্রিপ্ট করতে অক্ষম: %s ** + %1$s %2$s থেকে %3$s পর্যন্ত + %1$s %2$s এর পাওয়ার স্তর পরিবর্তন করেছে। + আপনি %1$s এর পাওয়ার স্তর পরিবর্তন করেছেন। + কাস্টম + কাস্টম (%1$d) + ডিফল্ট + নিয়ামক + অ্যাডমিন + আপনি %1$s উইজেট পরিবর্তন করেছেন + %1$s %2$s উইজেট পরিবর্তন করেছেন + আপনি %1$s উইজেট সরিয়েছেন + %1$s %2$s উইজেট সরিয়ে দিয়েছেন + আপনি %1$s উইজেট যুক্ত করেছেন + %1$s %2$s উইজেট যুক্ত করেছে + আপনি %1$s এর জন্য আমন্ত্রণটি গ্রহণ করেছেন + %1$s %2$s এর জন্য আমন্ত্রণটি গ্রহণ করেছে + আপনি %1$s এর কক্ষে যোগদানের জন্য আমন্ত্রণটি বাতিল করেছেন + %1$s %2$s এর কক্ষে যোগদানের আমন্ত্রণ বাতিল করে দিয়েছিল + আপনি %1$s কে ঘরে যোগদানের জন্য একটি আমন্ত্রণ প্রেরণ করেছেন + %1$s %2$s কে ঘরে যোগদানের জন্য একটি আমন্ত্রণ পাঠিয়েছে + আপনি আপনার প্রোফাইল %1$s আপডেট করেছেন + %1$s তাদের প্রোফাইল %2$s আপডেট করেছে + %1$s দ্বারা বার্তা সরানো হয়েছে [কারণ: %2$s] + বার্তা সরানো হয়েছে [কারণ:%1$s] + %1$s দ্বারা বার্তা সরানো হয়েছে + বার্তা সরানো হয়েছে + আপনি কক্ষের অবতার সরিয়েছেন + %1$s কক্ষের অবতার সরিয়ে নিয়েছে + আপনি কক্ষের বিষয়টিকে সরিয়ে দিয়েছেন + %1$s কক্ষের বিষয় মুছে ফেলেছে + আপনি কক্ষের নাম সরিয়েছেন + %1$s কক্ষের নাম সরিয়েছে + (আবতারটিও পরিবর্তন করা হয়েছিল) + ভিওআইপি সম্মেলন শেষ হয়েছে + ভিওআইপি সম্মেলন শুরু হয়েছে + আপনি একটি ভিওআইপি সম্মেলনের অনুরোধ করেছেন + %1$s একটি ভিওআইপি সম্মেলনের জন্য অনুরোধ করেছে + আপনি এই কক্ষটি আপগ্রেড করেছেন। + %s এই কক্ষটিকে আপগ্রেড করেছে। + আপনি শেষ-থেকে-শেষ এনক্রিপশন চালু করেছেন (%1$s) + %1$s এন্ড-টু-এন্ড এনক্রিপশন চালু করেছে (%2$s) + অজানা (%s)। + যে কেউ। + সমস্ত কক্ষের সদস্য। + কক্ষের সমস্ত সদস্য, যখন থেকে তারা যোগদান করেছিল। + কক্ষের সমস্ত সদস্য, যখন থেকে তারা আমন্ত্রিত। + আপনি ভবিষ্যতের কক্ষ ইতিহাস %1$s এর কাছে দৃশ্যমান করেছেন + %1$s ভবিষ্যতের ঘরের ইতিহাস %2$s এর কাছে দৃশ্যমান করে তুলেছে + আপনি কলটি শেষ করেছেন। + %s কলটি শেষ করেছেন। + আপনি কলটি উত্তর দিয়েছেন। + %s কলটির উত্তর দিয়েছে। + আপনি কল সেটআপ করার জন্য ডেটা প্রেরণ করেছেন। + কল সেটআপ করার জন্য %s ডেটা প্রেরণ করেছে। + আপনি একটি ভয়েস কল দিয়েছেন। + %s একটি ভয়েস কল দিয়েছে। + আপনি একটি ভিডিও কল করেছেন। + %s একটি ভিডিও কল স্থাপন করেছিল। + আপনি কক্ষের নাম এতে পরিবর্তন করেছেন:%1$s + %1$s রুম এর নাম এতে পরিবর্তন করেছে: %2$s + আপনি কক্ষের অবতারটি পরিবর্তন করেছেন + %1$s কক্ষের অবতারটি পরিবর্তন করেছে + আপনি বিষয়টিকে এতে পরিবর্তন করেছেন: %1$s + %1$s বিষয় টি এতে পরিবর্তন করেছে: %2$s + আপনি আপনার প্রদর্শনের নামটি সরিয়ে দিয়েছেন (যেটা ছিল %1$s) + %1$s নিজের প্রদর্শন নাম মুছে দিয়েছে (%2$s) + আপনি আপনার প্রদর্শনের নামটি %1$s থেকে %2$s এ পরিবর্তন করেছেন + %1$s নিজের প্রদর্শন নাম %2$s থেকে %3$s তে পরিবর্তন করেছে + আপনি আপনার প্রদর্শনের নামটি %1$s তে সেট করেছেন + %1$s নিজের প্রদর্শন নাম %2$s রেখেছে + আপনি আপনার অবতারটি পরিবর্তন করেছেন + %1$s নিজের অবতার পরিবর্তন করেছে + + ১ টি অপঠিত বিজ্ঞপ্তি বার্তা + %d টি অপঠিত বিজ্ঞপ্তি বার্তা + + + ১ টি অপঠিত বিজ্ঞপ্তি বার্তা + %d টি অপঠিত বিজ্ঞপ্তি বার্তা + + এখানে টাইপ করুন… + সমস্ত নেটিভ %s রুমগুলি + %s সার্ভার থেকে সব রুমগুলি + হোমসেরভের ইউআরএল + পাবলিক রুমগুলি তালিকায় পেতে একটি হোমসেরভের টাইপ করুন + সার্ভার অনুপলব্ধ বা ওভারলোড হতে পারে + একটি রুম ডিরেক্টরি নির্বাচন করুন + এই রুমে অজানা সেশান রয়েছে যা যাচাই করা হয়নি। +\nএর অর্থ এই যে সেশানগুলি যে ব্যবহারকারীদের দাবি করে সেগুলির সাথে সেশানগুলির কোনও নিশ্চয়তা নেই। +\nআমরা আপনাকে চালিয়ে যাওয়ার আগে প্রতিটি সেশানের জন্য যাচাইকরণ প্রক্রিয়ার মধ্য দিয়ে যেতে পরামর্শ দিই তবে আপনি যদি পছন্দ করেন তবে যাচাই না করে বার্তাটি পুনরায় পাঠাতে পারেন। +\n +\nঅজানা সেশানগুলি: + রুমে অজানা সেশান রয়েছে + আমি যে কি মেলে মেলে যাচাই + এটি মিললে, নীচের যাচাই বাটন টিপুন। যদি এটি না হয় তবে অন্য কেউ এই সেশানটিকে আটক করছে এবং আপনাকে সম্ভবত এটি কালো তালিকাভুক্ত করা উচিত। ভবিষ্যতে এই যাচাই প্রক্রিয়া আরো পরিশীলিত হবে। + এই সেশানটি বিশ্বাসযোগ্য হতে পারে তা যাচাই করতে, অন্য কোন উপায়ে (যেমন ব্যক্তি বা ফোন কল) ব্যবহার করে তার মালিকের সাথে যোগাযোগ করুন এবং এই সেশানটির জন্য তাদের ব্যবহারকারী সেটিংসে এ কুঞ্জি দেখছে তা তাদের জিজ্ঞাসা করুন নীচের কুঞ্জিটির সাথে মেলে কিনা: + সেশান যাচাই করুন + কালোতালিকাযুক্ত না + কালোতালিকা + অযাচাই + যাচাই করুন + কেউ না + অজ্ঞাত ip + অজ্ঞাত সেশান + কালোতালিকাভুক্ত + প্রতিপাদিত + যাচাই করা হয়নি + এই সেশান থেকে যাচাই করা সেশানগুলিতে এনক্রিপ্ট করা বার্তাগুলি কখনও প্রেরণ করবেন না। + শুধুমাত্র যাচাই সেশানে এনক্রিপ্ট করুন + ইম্পোর্ট + একটি স্থানীয় ফাইল থেকে কুঞ্জি ইম্পোর্ট করুন + রুমের কুঞ্জিগুলি ইমপোর্ট করুন + E2E রুমের কুনজিগুলি ইম্পোর্ট করুন + কুঞ্জি ব্যাকআপ পরিচালনা করুন + এনক্রিপ্ট করা বার্তা পুনরুদ্ধার + কীগুলি সফলভাবে উত্পাদন হয়েছিল + E2E রুম কীগুলি \'%s\' তে সংরক্ষিত হয়েছে। +\n +\nসতর্কতা: অ্যাপ্লিকেশন আনইনস্টল হলে এই ফাইল মুছে যেতে পারে। + এক্সপোর্ট করা কুঞ্জিগুলি এনক্রিপ্ট করার জন্য একটি পাসফ্রেজ তৈরি করুন। কুঞ্জিগুলি ইম্পোর্ট করতে সক্ষম হবার জন্য আপনাকে একই পাসফ্রেজটি প্রবেশ করতে হবে। + এক্সপোর্ট + একটি স্থানীয় ফাইলে কুঞ্জি এক্সপোর্ট করুন + রুমের কুঞ্জিগুলি এক্সপোর্ট করুন + শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন + Ed25519 ফিঙ্গারপ্রিন্ট + প্রতিপাদন + সেশানের কুঞ্জি + আইডি + সর্বজনীন নাম + একটি সেশানের সর্বজনীন নাম আপনি যাদের সাথে যোগাযোগ করেন তাদের কাছে দৃশ্যমান + সর্বজনীন নাম (যাদের সাথে আপনি যোগাযোগ করেন তাদের কাছে দৃশ্যমান) + সর্বজনীন নাম + প্রেরক সেশান তথ্য + ডিক্রিপশন সমস্যা + সেশন আইডি + অ্যালগরিদম + ED25519 ফিঙ্গারপ্রিন্ট কুঞ্জি স্বীকৃত + Curve25519 পরিচয় কুঞ্জি + ব্যবহারকারীর প্রমানপত্র + ঘটনা তথ্য + শেষ থেকে শেষ এনক্র্যাপশনের তথ্য + %s এই রুমের টাইমলাইনে একটি নির্দিষ্ট বিন্দু লোড করার চেষ্টা করছিল কিন্তু এটি খুঁজে পেতে অক্ষম। + থিম + নির্দেশিকা + এনক্রিপশন সক্রিয় করুন +\n(সতর্কতা: আবার নিষ্ক্রিয় করা যাবে না!) + এনক্রিপশন এই রুমে নিষ্ক্রিয় করা আছে। + এনক্রিপশন এই রুমে সক্রিয় করা আছে। + রুমের ঠিকানা অনুলিপি করুন + রুমের আইডি অনুলিপি করুন + প্রধান ঠিকানা হিসাবে আনসেট করুন + প্রধান ঠিকানা হিসাবে সেট করুন + প্রধান ঠিকানা সতর্কতা + আপনার এই রুমের জন্য নির্দিষ্ট কোন প্রধান ঠিকানা থাকবে না। + নিষেধাজ্ঞা মুক্ত + নিষেধাজ্ঞা + এই রুমে থেকে সরান + এই রুম ছাড়ো + আমন্ত্রণ বাতিল করুন + আমন্ত্রণ + সেশানগুলি + সরাসরি বার্তা + কল + অ্যাডমিন সরঞ্জাম + %1$s %2$s পূর্বে + এখুন %1$s + অলস + অফলাইন + অনলাইন + সৃষ্টি + আপনি কি এই চ্যাট থেকে %s মুছে ফেলতে চান\? + আপনি কি রুম ছেড়ে যেতে চান\? + রুম ছেড়ে দিন + + ১ দিন + %d দিন + + + ১ ঘন্টা + %d ঘন্টা + + + ১ মিনিট + %d মিনিট + + + ১ সেকেন্ড + %d সেকেন্ড + + ১ জন সদস্য + + ১ জন সদস্য + "%d জন সদস্য" + + + ১ সক্রিয় সদস্য + %d সক্রিয় সদস্য + + সদস্য যোগ করুন + নতুন বার্তা + এই ক্রিয়াটি সম্পাদন করতে আপনার সেটিংসে একটি পরিচয় সার্ভার যুক্ত করুন। + এটি এই রুমে একটি পূর্বরূপ। রুম মিথস্ক্রিয়া নিষ্ক্রিয় করা হয়েছে। + একটা রুম + আপনি %s অ্যাক্সেস করার চেষ্টা করছেন। আপনি কি আলোচনায় অংশ নিতে চান\? + এই আমন্ত্রণটি %s কে পাঠানো হয়েছিল, যা এই অ্যাকাউন্টের সাথে যুক্ত নয়। +\nআপনি একটি পৃথক অ্যাকাউন্ট দিয়ে লগইন করতে চান, অথবা আপনার অ্যাকাউন্টে এই ইমেল যোগ করতে পারেন। + আপনি এই রুমে %s দ্বারা যোগ দিতে আমন্ত্রিত হয়েছেন + প্রথম অপঠিত বার্তা তে ঝাঁপ দাও। + সিঙ্ক করা হচ্ছে… + হেডার খোলো + সদস্যদের তালিকা + প্রত্যাখ্যান + প্রিভিউ + যোগদান + অপসারণ + প্রলম্বিত + না + হ্যাঁ + ডাউনলোডে সংরক্ষণ করবেন\? + সংরক্ষিত + দুঃখিত। কর্ম সঞ্চালিত না, অনুপস্থিত অনুমতির জন্য + রায়ট অন্যান্য ম্যাট্রিক্স ব্যবহারকারীদের তাদের ইমেল এবং ফোন নম্বরগুলির উপর ভিত্তি করে আপনার ঠিকানা বইটি চেক করতে পারে। +\n +\nআপনি এই উদ্দেশ্যে আপনার ঠিকানা বই ভাগ করতে সম্মত হন\? + রায়ট অন্যান্য ম্যাট্রিক্স ব্যবহারকারীদের তাদের ইমেল এবং ফোন নম্বরগুলির উপর ভিত্তি করে আপনার ঠিকানা বইটি চেক করতে পারে। আপনি যদি এই উদ্দেশ্যে আপনার ঠিকানা বইটি ভাগ করে নিতে সম্মত হন তবে দয়া করে পরবর্তী পপ-আপটিতে অ্যাক্সেসের অনুমতি দিন। + ভিডিও কল সম্পাদনের জন্য ${app_name} আপনার ক্যামেরা এবং আপনার মাইক্রোফোন অ্যাক্সেস করার অনুমতির প্রয়োজন। +\n +\nকল করতে সক্ষম হতে পরবর্তী পপ আপ অ্যাক্সেস অনুমতি দিন। + " +\n +\nদেয়া করে অনুমতিদিন প্রবেশ করতে পরের pop-up এ যেটা কল করতে সক্ষম।" + ${app_name} এর প্রয়োজন অনুমতি নিয়ে প্রবেশ করতে আপনার মাইক্রোফোন আর মাধ্যমে শোনার কালএর সঞ্চালনা করতে। + " +\n +\nদয়াকরে অনুমতি দিন প্রবেশ করাতে পরের পপ -আপ কে যেটা ডাকতে সক্ষম।" + ${app_name} এর প্রয়োজন অনুমতি নিয়ে প্রবেশে করতে আপনার ছবি তোলার যন্ত্র থেকে ছবি নিতে এবং দৃষ্টি রেকর্ড ডাকতে। + ${app_name} এর প্রয়াজন অনুমতি নিতে আপনার ছবি এবং দৃশ্য রেকর্ড কে গ্রন্থাগার থেকে পাঠিয়ে জমার জায়গায় সংযুক্ত করতে। +\nদেয়া করে অনুমতি দিন প্রবেশ করতে পরের pop-up কে যেটা সক্ষম আপনার নথি কে আপনার ফোন থেকে পাঠাতে। + তথা + পারছেন না দৃশ্য নথি করতে + নাও একটা ছবি অথবা একটা দৃশ্য রেকর্ড + ডাক আর উত্তর দেওয়া হয়েছে অনত্র + ক্যামেরাটি আরাম্ভ করতে পারছেন না + মাধ্যম সংযোগ ব্যর্থ হয়েছে + দূরবর্তী এর একটি ধার নিতে ব্যর্থ হচ্ছে। + কল ফিরে যান + অ্যাক্টিভ কল (%s) + দৃশ্য-রেকর্ড ডাক এর অগ্রগতি… + ডাক এর অগ্রগতি… + প্রবেশ কথাবলার ডাক + প্রবেশ দৃশ্য-রেকর্ড ডাক + আসা ডাক + ডাকাহচ্ছে… + কল শেষ হলো + কল সংযোগ হচ্ছে।… + কল সংযুক্ত হয়েছিলো + ডাকা + নির্বাচন করুন রিংটোন কল আর জন্য: + আসা কল এর রিংটোন + আপনার হোমসার্ভার একটি প্রস্তাব না দিলে সহায়তা হিসাবে %s ব্যবহার করবে (আপনার আইপি ঠিকানা কল করার সময় ভাগ করা হবে) + ফ্যালব্যাক কল সহায়তা সার্ভারকে অনুমতি দিন + ব্যবহার করছেন অনুপস্থিত ${app_name} রিংটোন আগামী ডাক এর জন্য + ডাকা + ঘরএর বিষয় + ঘরএর নাম + আজ + গতকাল + %1$dm %2$ds + %d s + বাতিল করুন আপলোড\? + বাতিল করুন নামানো নথি\? + ছোট + মাধ্যম + বড় + খাঁটি + আপনি %1$s এর আমন্ত্রণ প্রত্যাহার করেছেন + %1$s %2$s এর আমন্ত্রণ ফেরত নিয়েছে + আপনি %1$s কে নিষিদ্ধ করেছেন + %1$s %2$s কে নিষিদ্ধ করেছে + আপনি %1$s কে নিষিদ্ধ মুক্ত করেছেন + %1$s %2$s কে নিষিদ্ধ তালিকা থেকে মুক্ত করেছে + আপনি %1$s কে কীক করেছেন + %1$s %2$s কে কিক করেছে + আপনি আমন্ত্রণটি বাতিল করেছেন + %1$s আমন্ত্রণ টি বাতিল করেছে + আপনি কক্ষ ছেড়ে দিয়েছেন + %1$s রুম ছেড়ে দিয়েছে + আপনি কক্ষে যোগ দিয়েছেন + %1$s রুম এ যোগ দিয়েছে + %1$s আপনাকে আমন্ত্রণ করেছে + আপনি %1$s কে আমন্ত্রিত করেছেন + %1$s %2$s কে আমন্ত্রণ করেছে + আপনি কক্ষটি তৈরি করেছেন + %1$s কক্ষটি তৈরি করেছেন + আপনার আমন্ত্রণ + %s এর আমন্ত্রণ + আপনি একটি স্তিকার পাঠিয়েছেন। + %1$s একটি স্তিকার পাঠিয়েছে। + %1$s: %2$s + \ No newline at end of file From 6c30e90dd9de4687f32b6aba5608b34d4478a899 Mon Sep 17 00:00:00 2001 From: Jeanne Lavoie Date: Sat, 10 Apr 2021 17:05:04 +0000 Subject: [PATCH 038/230] Translated using Weblate (Italian) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index fcb805ef30..2438b5d2ce 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -599,8 +599,8 @@ Desideri nascondere tutti i messaggi di questo utente\? \n \nTieni presente che questa azione riavvierà l\'app e ciò potrebbe richiedere molto tempo. - Annulla l\'upload - Annulla il download + Annulla il caricamento + Annulla lo scaricamento Cerca Cerca tra i membri della stanza @@ -1084,20 +1084,20 @@ Clicca qui per vedere i messaggi precedenti Non hai permessi sufficienti per effettuare questa azione. - %ds - %ds + %d s + %d s - %dm - %dm + %d min + %d min - %dh - %dh + %d h + %d h - %dd - %dd + %d g. + %d g. %1$s ora %1$s %2$s fa From 984342a6ddc2f9a2d4c677c8c331c14bab2b105f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sun, 11 Apr 2021 21:07:08 +0200 Subject: [PATCH 039/230] Upgrade com.likethesalad.android:string-reference to 1.2.2 Compatible with gradle7, see https://github.com/LikeTheSalad/android-string-reference/issues/11 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d645382015..3268cfa084 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3' - classpath "com.likethesalad.android:string-reference:1.2.1" + classpath "com.likethesalad.android:string-reference:1.2.2" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 5ca71a10ec9d8aa0f2c1c2ae1a957c453937f5e8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 12 Apr 2021 10:46:03 +0200 Subject: [PATCH 040/230] Changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index a73551cc33..79fc7eba95 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,7 @@ SDK API changes ⚠️: - Build 🧱: - - + - Upgrade to gradle 7 Test: - From bb9258cc47b68ad429a6ab2e9d3aebdc5ee20666 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Apr 2021 07:21:36 +0000 Subject: [PATCH 041/230] Bump oss-licenses-plugin from 0.10.3 to 0.10.4 Bumps oss-licenses-plugin from 0.10.3 to 0.10.4. Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3268cfa084..537a78992d 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.5' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1' - classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3' + classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4' classpath "com.likethesalad.android:string-reference:1.2.2" // NOTE: Do not place your application dependencies here; they belong From 05b35f53d416adb26350eb586cec5f1d54885f48 Mon Sep 17 00:00:00 2001 From: Thai Localization Date: Sun, 11 Apr 2021 19:59:29 +0000 Subject: [PATCH 042/230] Translated using Weblate (Thai) Currently translated at 12.2% (290 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/th/ --- vector/src/main/res/values-th/strings.xml | 114 +++++++++++++++------- 1 file changed, 80 insertions(+), 34 deletions(-) diff --git a/vector/src/main/res/values-th/strings.xml b/vector/src/main/res/values-th/strings.xml index c677202fe5..a89dc740ac 100644 --- a/vector/src/main/res/values-th/strings.xml +++ b/vector/src/main/res/values-th/strings.xml @@ -14,19 +14,19 @@ ข้อความ ห้อง การตั้งค่า - ข้อมูลสมาชิก + รายละเอียดสมาชิก ตกลง ยกเลิก บันทึก ออก ส่ง ดาวน์โหลด - แชร์ + แบ่งปัน พูด ลบ เปลี่ยนชื่อ ข้าม - ส่งสติกเกอร์ + ส่งสติ๊กเกอร์ กำลังโหลด… ส่งต่อ ไม่มี @@ -40,13 +40,13 @@ ผู้คน ห้อง ชุมชน - การเชิญ + คำเชิญ การแจ้งเตือนจากระบบ คุณไม่ได้อนุญาตให้ ${app_name} เข้าถึงรายชื่อผู้ติดต่อในเครื่อง ไม่มีผลลัพธ์ ไม่มีห้อง - ผู้ใช้ %d คน + %d ผู้ใช้ เชิญ ชุมชน @@ -54,11 +54,11 @@ ถ้าเป็นไปได้ โปรดเขียนคำอธิบายเป็นภาษาอังกฤษ อธิบายปัญหาของคุณที่นี่ ส่งไปยัง - เข้าห้อง + เข้าร่วมห้อง ชื่อผู้ใช้ สร้างบัญชี เข้าสู่ระบบ - ออกจากระบบ + ลงชื่อออก ค้นหา เริ่มแชทใหม่ ลองโดยใช้ %s @@ -73,7 +73,7 @@ หลัง ปิด HD เปิด HD - ส่งสติกเกอร์ + ส่งสติ๊กเกอร์ เข้าสู่ระบบ สร้างบัญชี ส่ง @@ -94,11 +94,11 @@ ชื่อห้อง วันนี้ เมื่อวาน - %1$d นาที %2$d วิ - %d วิ - ขนาดเล็ก - ขนาดกลาง - ขนาดใหญ่ + %1$d นาที %2$d วินาที + %d วินาที + เล็ก + กลาง + ใหญ่ ต้นฉบับ ส่งเป็น รายการกลุ่ม @@ -136,14 +136,14 @@ ตอบกลับด่วน การโทรวิดีโอ การโทรเสียง - ออกจากระบบ + ลงชื่อออก ออก วางสาย ปฏิเสธ ยอมรับ ปฏิเสธ เพิกเฉย - เรียบร้อย + เสร็จสิ้น ยอมรับ ออฟไลน์ เชิญ @@ -151,22 +151,22 @@ ไม่สามารถเริ่มการโทร เริ่มการประชุมเสียง เริ่มการประชุมวิดีโอ - เริ่มสนทนา + เริ่มแชท รีเซ็ต ปิดทิ้ง เลิกเชื่อมต่อ ดูต้นฉบับที่ถอดรหัสแล้ว ดูต้นฉบับ - ไว้ภายหลัง - ลบออก - ส่งซ้ำ + ภายหลัง + เอาออก + ส่งใหม่ อยู่ สำรองข้อมูล คุณแน่ใจหรือไม่\? - ข้อมูลชุมชน + รายละเอียดชุมชน ค่าเริ่มต้นของระบบ เพิ่มที่อยู่อีเมล - หมายเลขโทรศัพท์ + โทรศัพท์ พบ %1$s ห้องสำหรับ %2$s @@ -189,10 +189,10 @@ การตั้งค่า ไฟล์ ผู้คน - ข้อมูลห้อง + รายละเอียดห้อง เปลี่ยนหัวข้อ อัปเกรดห้อง - เปิดใช้การเข้ารหัสห้อง + เปิดใช้งานการเข้ารหัสห้อง เปลี่ยนการตั้งค่า เชิญผู้ใช้ ส่งข้อความ @@ -204,7 +204,7 @@ ส่งข้อความ (ไม่เข้ารหัส)… ส่งข้อความที่เข้ารหัส… - %1$s และ %2$s และคนอื่น ๆ กำลังพิมพ์… + %1$s และ %2$s และอื่น ๆ กำลังพิมพ์… %1$s และ %2$s กำลังพิมพ์… %s กำลังพิมพ์… ค้นหา @@ -212,8 +212,8 @@ %1$s %2$s เหตุผล เตะ - เลิกแบน - แบน + เลิกห้าม + ห้าม ออกจากห้องนี้ เชิญ ข้อความโดยตรง @@ -225,16 +225,16 @@ ออฟไลน์ ออนไลน์ สร้าง - คุณแน่ใจหรือไม่ว่าต้องการออกจากห้อง + คุณแน่ใจหรือไม่ว่าต้องการออกจากห้อง\? ออกจากห้อง - %d วิ + %d วินาที %d นาที - %d ชม. + %d ชั่วโมง %d วัน @@ -245,7 +245,7 @@ ไปที่ข้อความแรกที่ยังไม่ได้อ่าน แสดงรายชื่อสมาชิก ปฏิเสธ - ตัวอย่าง + แสดงตัวอย่าง เข้าร่วม ดำเนินการต่อ ไม่ @@ -254,14 +254,60 @@ ต้องยืนยันว่าต้องการโทรจริง ๆ ก่อนที่จะเริ่มการโทร ป้องกันการโทรโดยไม่ได้ตั้งใจ หัวข้อห้อง - ยกเลิกการอัปโหลดหรือไม่\? - ยกเลิกการดาวน์โหลดหรือไม่\? + ยกเลิกการอัปโหลด\? + ยกเลิกการดาวน์โหลด\? ส่งคำขอแล้ว - โปรดระบุ URL ที่ถูกต้อง + โปรดป้อน URL ที่ถูกต้อง URL ต้องขึ้นต้นด้วย http[s]:// ส่งข้อความเสียง เฉพาะรายชื่อผู้ติดต่อ Matrix ไม่มีห้องสาธารณะ ยกเลิก - ระบุทั้งหมดว่าอ่านแล้ว + ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว + %1$s ได้ส่งสติ๊กเกอร์ + คุณได้ส่งสติ๊กเกอร์ + คำเชิญของ %s + คำเชิญของคุณ + %1$s ได้สร้างห้อง + %1$s ได้สร้างการสนทนา + คุณได้สร้างการสนทนา + %1$s ได้เชิญ %2$s + คุณได้เชิญ %1$s + %1$s ได้เข้าร่วมห้อง + คุณได้เข้าร่วมห้อง + %1$s ได้เข้าร่วม + คุณได้ออกจากห้อง + คุณได้สร้างห้อง + %1$s ได้เชิญคุณ + คุณได้เข้าร่วม + %1$s ได้ออกจากห้อง + ลงชื่อเข้า + ลงชื่อเข้า + ถัดไป + ถัดไป + ลงชื่อเข้า + ลงทะเบียน + กำลังค้นหาไดเรกทอรี… + เรียกดูไดเรกทอรี + เข้าร่วมห้อง + เข้าร่วมห้อง + สร้างห้อง + เริ่มแชท + ห้อง + ไดเรกทอรี + เข้าร่วมแล้ว + ไดเรกทอรีผู้ใช้ (%s) + ทำให้เป็นผู้ดูแล + ทำให้เป็นผู้ควบคุม + เอาออกจากห้องนี้ + 1 สมาชิก + + %d สมาชิก + + ส่งไฟล์ + ไดเรกทอรีผู้ใช้ + ล้าง + กำลังซิงค์… + เอาออก + รายการโปรด \ No newline at end of file From f23eb425a13e9a0d78504d74e4e373cd562722d5 Mon Sep 17 00:00:00 2001 From: Salamandar Date: Mon, 12 Apr 2021 08:51:14 +0000 Subject: [PATCH 043/230] Translated using Weblate (French) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr/full_description.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fastlane/metadata/android/fr/full_description.txt b/fastlane/metadata/android/fr/full_description.txt index 7604384dd1..066b94868b 100644 --- a/fastlane/metadata/android/fr/full_description.txt +++ b/fastlane/metadata/android/fr/full_description.txt @@ -17,6 +17,8 @@ Element vous donne le contrôle en vous laissant choisir qui héberge vos conver 2. Héberger vous-même votre compte en installant un serveur sur votre propre machine 3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS) +Pourquoi choisir Element ? + VOS DONNÉES VOUS APPARTIENNENT : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant. MESSAGERIE ET COLLABORATION OUVERTES : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP. From ce7e00a499c7adf0aaf8d5fec559ad7a3a6e82fa Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 13 Apr 2021 11:48:12 +0300 Subject: [PATCH 044/230] Fix send state image color on dark theme. --- CHANGES.md | 2 +- vector/src/main/res/layout/item_timeline_event_base.xml | 2 ++ vector/src/main/res/values/colors.xml | 6 ++---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3012560ed6..9c50c2a2ee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Message states cosmetic changes (#3007) Translations 🗣: - diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index f9562f65b0..0515ace033 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -145,6 +145,7 @@ android:layout_marginBottom="4dp" android:contentDescription="@string/event_status_a11y_sending" android:src="@drawable/ic_sending_message" + android:tint="?riotx_text_tertiary" android:visibility="gone" tools:visibility="visible" /> @@ -158,6 +159,7 @@ android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="4dp" + android:tint="?riotx_text_tertiary" android:visibility="gone" tools:visibility="visible" /> diff --git a/vector/src/main/res/values/colors.xml b/vector/src/main/res/values/colors.xml index 7561e6f843..f4a026bea3 100644 --- a/vector/src/main/res/values/colors.xml +++ b/vector/src/main/res/values/colors.xml @@ -198,10 +198,8 @@ #FF8D99A5 - - #FF8D99A5 - - #FF8D99A5 + #FF8E99A4 + #FF8E99A4 #FF61708B From 2116ff91cc9eeb8d45870f574e49f4b18c3ac6b2 Mon Sep 17 00:00:00 2001 From: Moo Date: Tue, 13 Apr 2021 19:52:50 +0000 Subject: [PATCH 045/230] Added translation using Weblate (Lithuanian) --- fastlane/metadata/android/nb/changelogs/40100170.txt | 2 ++ fastlane/metadata/android/nb/changelogs/40101010.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/nb/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/nb/changelogs/40101010.txt diff --git a/fastlane/metadata/android/nb/changelogs/40100170.txt b/fastlane/metadata/android/nb/changelogs/40100170.txt new file mode 100644 index 0000000000..3593e50e05 --- /dev/null +++ b/fastlane/metadata/android/nb/changelogs/40100170.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: Bugfikser! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/nb/changelogs/40101010.txt b/fastlane/metadata/android/nb/changelogs/40101010.txt new file mode 100644 index 0000000000..2d80855ed8 --- /dev/null +++ b/fastlane/metadata/android/nb/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: Forbedringer i ytelse og bugfikser! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1 From afe617357db373bc84e2a5935113c6060751e273 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Apr 2021 07:08:48 +0000 Subject: [PATCH 046/230] Bump zxcvbn from 1.4.0 to 1.4.1 Bumps [zxcvbn](https://github.com/nulab/zxcvbn4j) from 1.4.0 to 1.4.1. - [Release notes](https://github.com/nulab/zxcvbn4j/releases) - [Changelog](https://github.com/nulab/zxcvbn4j/blob/master/CHANGELOG.md) - [Commits](https://github.com/nulab/zxcvbn4j/compare/1.4.0...1.4.1) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 760a5d0743..a47ab84f7a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -388,7 +388,7 @@ dependencies { implementation 'androidx.browser:browser:1.3.0' // Passphrase strength helper - implementation 'com.nulab-inc:zxcvbn:1.4.0' + implementation 'com.nulab-inc:zxcvbn:1.4.1' //Alerter implementation 'com.tapadoo.android:alerter:7.0.1' From 39f304a57b4bd5d09f990a4d3df66afaf09e4272 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Apr 2021 07:09:03 +0000 Subject: [PATCH 047/230] Bump big_image_viewer_version from 1.7.1 to 1.8.0 Bumps `big_image_viewer_version` from 1.7.1 to 1.8.0. Updates `BigImageViewer` from 1.7.1 to 1.8.0 - [Release notes](https://github.com/Piasy/BigImageViewer/releases) - [Changelog](https://github.com/Piasy/BigImageViewer/blob/master/CHANGELOG.md) - [Commits](https://github.com/Piasy/BigImageViewer/commits) Updates `GlideImageLoader` from 1.7.1 to 1.8.0 - [Release notes](https://github.com/Piasy/BigImageViewer/releases) - [Changelog](https://github.com/Piasy/BigImageViewer/blob/master/CHANGELOG.md) - [Commits](https://github.com/Piasy/BigImageViewer/commits) Updates `ProgressPieIndicator` from 1.7.1 to 1.8.0 - [Release notes](https://github.com/Piasy/BigImageViewer/releases) - [Changelog](https://github.com/Piasy/BigImageViewer/blob/master/CHANGELOG.md) - [Commits](https://github.com/Piasy/BigImageViewer/commits) Updates `GlideImageViewFactory` from 1.7.1 to 1.8.0 - [Release notes](https://github.com/Piasy/BigImageViewer/releases) - [Changelog](https://github.com/Piasy/BigImageViewer/blob/master/CHANGELOG.md) - [Commits](https://github.com/Piasy/BigImageViewer/commits) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 760a5d0743..aec8f4be0b 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -294,7 +294,7 @@ dependencies { def fragment_version = '1.3.2' def arrow_version = "0.8.2" def markwon_version = '4.1.2' - def big_image_viewer_version = '1.7.1' + def big_image_viewer_version = '1.8.0' def glide_version = '4.12.0' def moshi_version = '1.12.0' def daggerVersion = '2.34' From 30b415e77ecdb1a4c381c5c6936e647107112b00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Apr 2021 07:09:19 +0000 Subject: [PATCH 048/230] Bump daggerVersion from 2.34 to 2.34.1 Bumps `daggerVersion` from 2.34 to 2.34.1. Updates `dagger` from 2.34 to 2.34.1 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.34...dagger-2.34.1) Updates `dagger-compiler` from 2.34 to 2.34.1 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.34...dagger-2.34.1) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 669444d563..42519bf95a 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -112,7 +112,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.34' + def daggerVersion = '2.34.1' def work_version = '2.5.0' def retrofit_version = '2.9.0' diff --git a/vector/build.gradle b/vector/build.gradle index 760a5d0743..0595c0c510 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -297,7 +297,7 @@ dependencies { def big_image_viewer_version = '1.7.1' def glide_version = '4.12.0' def moshi_version = '1.12.0' - def daggerVersion = '2.34' + def daggerVersion = '2.34.1' def autofill_version = "1.1.0" def work_version = '2.5.0' def arch_version = '2.1.0' From e467e2ca08a973fa6a649e52a7cad180797a276e Mon Sep 17 00:00:00 2001 From: yostyle Date: Wed, 14 Apr 2021 11:35:21 +0200 Subject: [PATCH 049/230] Expose directUserId in RoomSummary --- .../org/matrix/android/sdk/api/session/room/model/RoomSummary.kt | 1 + .../android/sdk/internal/database/mapper/RoomSummaryMapper.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index 9455a83aff..8a2aecd76d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -36,6 +36,7 @@ data class RoomSummary constructor( val canonicalAlias: String? = null, val aliases: List = emptyList(), val isDirect: Boolean = false, + val directUserId: String? = null, val joinedMembersCount: Int? = 0, val invitedMembersCount: Int? = 0, val latestPreviewableEvent: TimelineEvent? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 6dc70b60fc..d6b46ae436 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -43,6 +43,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", isDirect = roomSummaryEntity.isDirect, + directUserId = roomSummaryEntity.directUserId ?: "", latestPreviewableEvent = latestEvent, joinedMembersCount = roomSummaryEntity.joinedMembersCount, invitedMembersCount = roomSummaryEntity.invitedMembersCount, From 699b1429b73cb217bf9cafd32bb65349f33aed11 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 14 Apr 2021 12:42:47 +0200 Subject: [PATCH 050/230] Simplify `LoginFlowResult` model --- .../sdk/api/auth/data/LoginFlowResult.kt | 16 +++--- .../auth/DefaultAuthenticationService.kt | 28 +++++----- .../app/features/login/LoginViewModel.kt | 52 +++++++++---------- .../signout/soft/SoftLogoutViewModel.kt | 28 +++++----- 4 files changed, 60 insertions(+), 64 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt index f1f9ba3916..7d1407c0d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt @@ -16,12 +16,10 @@ package org.matrix.android.sdk.api.auth.data -sealed class LoginFlowResult { - data class Success( - val supportedLoginTypes: List, - val ssoIdentityProviders: List?, - val isLoginAndRegistrationSupported: Boolean, - val homeServerUrl: String, - val isOutdatedHomeserver: Boolean - ) : LoginFlowResult() -} +data class LoginFlowResult( + val supportedLoginTypes: List, + val ssoIdentityProviders: List?, + val isLoginAndRegistrationSupported: Boolean, + val homeServerUrl: String, + val isOutdatedHomeserver: Boolean +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index e26286ad2f..46256f4b81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -144,16 +144,14 @@ internal class DefaultAuthenticationService @Inject constructor( } return result.fold( { - if (it is LoginFlowResult.Success) { - // The homeserver exists and up to date, keep the config - // Homeserver url may have been changed, if it was a Riot url - val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(it.homeServerUrl) - ) + // The homeserver exists and up to date, keep the config + // Homeserver url may have been changed, if it was a Riot url + val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( + homeServerUri = Uri.parse(it.homeServerUrl) + ) - pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) - .also { data -> pendingSessionStore.savePendingSessionData(data) } - } + pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) + .also { data -> pendingSessionStore.savePendingSessionData(data) } it }, { @@ -307,12 +305,12 @@ internal class DefaultAuthenticationService @Inject constructor( val loginFlowResponse = executeRequest(null) { authAPI.getLoginFlows() } - return LoginFlowResult.Success( - loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, - loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, - versions.isLoginAndRegistrationSupportedBySdk(), - homeServerUrl, - !versions.isSupportedBySdk() + return LoginFlowResult( + supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, + ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, + isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(), + homeServerUrl = homeServerUrl, + isOutdatedHomeserver = !versions.isSupportedBySdk() ) } diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index 792c74e019..dd1b024b31 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -773,34 +773,34 @@ class LoginViewModel @AssistedInject constructor( null } - if (data is LoginFlowResult.Success) { - // Valid Homeserver, add it to the history. - // Note: we add what the user has input, data.homeServerUrl can be different - rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) + data ?: return@launch - val loginMode = when { - // SSO login is taken first - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) - && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } + // Valid Homeserver, add it to the history. + // Note: we add what the user has input, data.homeServerUrl can be different + rememberHomeServer(homeServerConnectionConfig.homeServerUri.toString()) - // FIXME We should post a view event here normally? - setState { - copy( - asyncHomeServerLoginFlowRequest = Uninitialized, - homeServerUrl = data.homeServerUrl, - loginMode = loginMode, - loginModeSupportedTypes = data.supportedLoginTypes.toList() - ) - } - if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) - || data.isOutdatedHomeserver) { - // Notify the UI - _viewEvents.post(LoginViewEvents.OutdatedHomeserver) - } + val loginMode = when { + // SSO login is taken first + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) + && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported + } + + // FIXME We should post a view event here normally? + setState { + copy( + asyncHomeServerLoginFlowRequest = Uninitialized, + homeServerUrl = data.homeServerUrl, + loginMode = loginMode, + loginModeSupportedTypes = data.supportedLoginTypes.toList() + ) + } + if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) + || data.isOutdatedHomeserver) { + // Notify the UI + _viewEvents.post(LoginViewEvents.OutdatedHomeserver) } } } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 2c0b78a546..4bce45dfc2 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -100,21 +100,21 @@ class SoftLogoutViewModel @AssistedInject constructor( null } - if (data is LoginFlowResult.Success) { - val loginMode = when { - // SSO login is taken first - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) - && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) - data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password - else -> LoginMode.Unsupported - } + data ?: return@launch - setState { - copy( - asyncHomeServerLoginFlowRequest = Success(loginMode) - ) - } + val loginMode = when { + // SSO login is taken first + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) + && data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.SsoAndPassword(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso(data.ssoIdentityProviders) + data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password + else -> LoginMode.Unsupported + } + + setState { + copy( + asyncHomeServerLoginFlowRequest = Success(loginMode) + ) } } } From 89c2662e09ea290ff6a00011d7b967d5fb830dd7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 14 Apr 2021 12:44:48 +0200 Subject: [PATCH 051/230] Fix social login button rendering on old OS --- .../vector/app/features/login/LoginFragment.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 5ba1fa394b..85ed6702ad 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -91,18 +91,29 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment { views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME) views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD) - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP } SignMode.SignIn, SignMode.SignInWithMatrixId -> { views.loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME) views.passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD) - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN } }.exhaustive } } + private fun setupSocialLoginButtons(state: LoginViewState) { + when (state.signMode) { + SignMode.Unknown -> error("developer error") + SignMode.SignUp -> { + views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP + } + SignMode.SignIn, + SignMode.SignInWithMatrixId -> { + views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN + } + }.exhaustive + } + private fun submit() { cleanupUi() @@ -277,6 +288,7 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment Date: Wed, 14 Apr 2021 12:48:33 +0200 Subject: [PATCH 052/230] compact code --- .../java/im/vector/app/features/login/LoginFragment.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt index 85ed6702ad..389895c179 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt @@ -102,15 +102,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment error("developer error") - SignMode.SignUp -> { - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_UP - } + SignMode.SignUp -> SocialLoginButtonsView.Mode.MODE_SIGN_UP SignMode.SignIn, - SignMode.SignInWithMatrixId -> { - views.loginSocialLoginButtons.mode = SocialLoginButtonsView.Mode.MODE_SIGN_IN - } + SignMode.SignInWithMatrixId -> SocialLoginButtonsView.Mode.MODE_SIGN_IN }.exhaustive } From 057295c155b206ff6b0498c081707a7c1b5268ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 14 Apr 2021 20:12:57 +0200 Subject: [PATCH 053/230] Cleanup --- .../src/main/java/im/vector/app/features/login/LoginViewModel.kt | 1 - .../im/vector/app/features/signout/soft/SoftLogoutViewModel.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt index dd1b024b31..ce68ff4f38 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt @@ -43,7 +43,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig -import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.FlowResult diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt index 4bce45dfc2..5fa14b7f08 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt @@ -33,7 +33,6 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.login.LoginMode import kotlinx.coroutines.launch import org.matrix.android.sdk.api.auth.AuthenticationService -import org.matrix.android.sdk.api.auth.data.LoginFlowResult import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.session.Session import timber.log.Timber From d6e495f79f826e241bb05a335e6841c4c46b42c1 Mon Sep 17 00:00:00 2001 From: Sven Grewe Date: Tue, 13 Apr 2021 10:20:18 +0000 Subject: [PATCH 054/230] Translated using Weblate (German) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index c60e226b7b..1899bfdf59 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2735,4 +2735,5 @@ Senden der Nachricht gescheitert Wird gesendet Nachricht gesendet + Zuerst nachfragen \ No newline at end of file From b683c45bd860342195acba9e6191b7b37e048ce5 Mon Sep 17 00:00:00 2001 From: Sven Grewe Date: Wed, 14 Apr 2021 17:23:18 +0000 Subject: [PATCH 055/230] Translated using Weblate (German) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ --- fastlane/metadata/android/de/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/de/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/de/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/de/changelogs/40101030.txt diff --git a/fastlane/metadata/android/de/changelogs/40101020.txt b/fastlane/metadata/android/de/changelogs/40101020.txt new file mode 100644 index 0000000000..32fabf7c2f --- /dev/null +++ b/fastlane/metadata/android/de/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/de/changelogs/40101030.txt b/fastlane/metadata/android/de/changelogs/40101030.txt new file mode 100644 index 0000000000..7e6dc25033 --- /dev/null +++ b/fastlane/metadata/android/de/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From 80e67281c6b41296c3491ac389251f6b76bb723c Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 14 Apr 2021 13:26:09 +0000 Subject: [PATCH 056/230] Translated using Weblate (Japanese) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/ --- .../android/ja/changelogs/40100100.txt | 2 ++ .../android/ja/changelogs/40100110.txt | 2 ++ .../android/ja/changelogs/40100120.txt | 2 ++ .../android/ja/changelogs/40100130.txt | 2 ++ .../android/ja/changelogs/40100140.txt | 2 ++ .../android/ja/changelogs/40100150.txt | 2 ++ .../android/ja/changelogs/40100160.txt | 2 ++ .../android/ja/changelogs/40100170.txt | 2 ++ .../android/ja/changelogs/40101000.txt | 2 ++ .../android/ja/changelogs/40101010.txt | 2 ++ .../android/ja/changelogs/40101020.txt | 2 ++ .../android/ja/changelogs/40101030.txt | 2 ++ .../metadata/android/ja/full_description.txt | 30 +++++++++++++++++++ .../metadata/android/ja/short_description.txt | 1 + fastlane/metadata/android/ja/title.txt | 1 + 15 files changed, 56 insertions(+) create mode 100644 fastlane/metadata/android/ja/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100110.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100120.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100130.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100140.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100150.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100160.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40101010.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/ja/changelogs/40101030.txt create mode 100644 fastlane/metadata/android/ja/full_description.txt create mode 100644 fastlane/metadata/android/ja/short_description.txt create mode 100644 fastlane/metadata/android/ja/title.txt diff --git a/fastlane/metadata/android/ja/changelogs/40100100.txt b/fastlane/metadata/android/ja/changelogs/40100100.txt new file mode 100644 index 0000000000..8359a12964 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100100.txt @@ -0,0 +1,2 @@ +今回の新バージョンでは、主にバグの修正と改善が行われています。メッセージの送信がより速くなりました。 +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/ja/changelogs/40100110.txt b/fastlane/metadata/android/ja/changelogs/40100110.txt new file mode 100644 index 0000000000..c93db421af --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100110.txt @@ -0,0 +1,2 @@ +今回の新バージョンでは、主にUI(ユーザーインターフェース)とUX(ユーザーエクスペリエンス)の向上が図られています。友達を招待したり、QRコードを読み取って素早くDMを作成できるようになりました。 +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.11 diff --git a/fastlane/metadata/android/ja/changelogs/40100120.txt b/fastlane/metadata/android/ja/changelogs/40100120.txt new file mode 100644 index 0000000000..aace2ef79f --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100120.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.12 diff --git a/fastlane/metadata/android/ja/changelogs/40100130.txt b/fastlane/metadata/android/ja/changelogs/40100130.txt new file mode 100644 index 0000000000..97633621c5 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100130.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/ja/changelogs/40100140.txt b/fastlane/metadata/android/ja/changelogs/40100140.txt new file mode 100644 index 0000000000..c340663127 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100140.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: 部屋の許可、自動のテーマ切替、そして多くのバグを修正しました。 +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/ja/changelogs/40100150.txt b/fastlane/metadata/android/ja/changelogs/40100150.txt new file mode 100644 index 0000000000..42f28c7bea --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100150.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: ソーシャルログインに対応しました。 +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/ja/changelogs/40100160.txt b/fastlane/metadata/android/ja/changelogs/40100160.txt new file mode 100644 index 0000000000..8b5196998a --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100160.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: パフォーマンスの向上とバグの修正! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/ja/changelogs/40100170.txt b/fastlane/metadata/android/ja/changelogs/40100170.txt new file mode 100644 index 0000000000..586b01cb2b --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40100170.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: バグの修正! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/ja/changelogs/40101000.txt b/fastlane/metadata/android/ja/changelogs/40101000.txt new file mode 100644 index 0000000000..25bbd7ab87 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40101000.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: パフォーマンスの向上とバグの修正! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/ja/changelogs/40101010.txt b/fastlane/metadata/android/ja/changelogs/40101010.txt new file mode 100644 index 0000000000..35ba933069 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40101010.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: パフォーマンスの向上とバグの修正! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/ja/changelogs/40101020.txt b/fastlane/metadata/android/ja/changelogs/40101020.txt new file mode 100644 index 0000000000..88e3c79ca8 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40101020.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: パフォーマンスの向上とバグの修正! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/ja/changelogs/40101030.txt b/fastlane/metadata/android/ja/changelogs/40101030.txt new file mode 100644 index 0000000000..87d191b226 --- /dev/null +++ b/fastlane/metadata/android/ja/changelogs/40101030.txt @@ -0,0 +1,2 @@ +このバージョンの主な変更点: パフォーマンスの向上とバグの修正! +すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.3 diff --git a/fastlane/metadata/android/ja/full_description.txt b/fastlane/metadata/android/ja/full_description.txt new file mode 100644 index 0000000000..855eb309c9 --- /dev/null +++ b/fastlane/metadata/android/ja/full_description.txt @@ -0,0 +1,30 @@ +Elementはまったく新しいタイプのメッセンジャーアプリです。 + +1. あなた自身がプライバシーをコントロールすることを可能にします。 +2. Matrixネットワークにいる誰とでも通信できることはもちろん、Slackなどのアプリとの連携によって他のネットワークとも通信ができます。 +3. 広告、データ収集、バックドア、ユーザーの囲い込みから逃れることができます。 +4. エンドツーエンド暗号化とクロス署名によってあなたを保護します。 + +Elementは非中央集権型でオープンソースであるため、他のメッセンジャーアプリとは完全に異なっています。 + +Elementはあなた自身でサーバーをホストすることも、サーバーを選ぶこともできます。これによってあなたのデータと会話に関するプライバシーや所有権はあなた自身で管理できるようになります。さらに、あなたは他のElementユーザーと話せるだけでなくオープンネットワークへのアクセスも可能です。とてもセキュアです。 + +Elementは、オープンな分散型通信の標準規格であるMatrixで動作するため、これらすべてを実現することができています。 + +Elementではあなたの会話をどのサーバーでホストするか決めることができます。アプリでは、さまざまな方法で選択できます。 + +1. matrix.orgの公開サーバーで無料のアカウントを取得します。 +2. あなた自身のハードウェアでサーバーを動かし、アカウントを管理します。 +3. Element Matrix Servicesのホスティングプラットフォームに登録することで、カスタムサーバー上のアカウントを取得できます。 + +なぜElementを選ぶべきなのか? + +データの所有権: 自分でデータやメッセージを保管する場所を決めることができます。あなたが所有権を持ってコントロールすることで、第三者にあなたのデータを渡したり、ビッグデータを収集する巨大テック企業に依存する必要がなくなります。 + +開かれたネットワークと共同作業: Matrixネットワーク内の他の誰とでも、あるいはElementや他のMatrixアプリを使っているかどうかに関わらず、またSlack、IRC、XMPPのような他のメッセージングシステムを使っているかどうかに関わらず、チャットすることができます。 + +はるかに安全: 本物のエンドツーエンド暗号化(会話に参加している者のみがメッセージを読める)と会話参加者の真正性を確認するためクロス署名によって。 + +完全なるコミュニケーションの訪れ: テキスト、音声通話、ビデオ通話、ファイル共有、画面共有、連携機能、ボット、ウィジェットなどのコミュニケーションに必要な機能の全てが実装されています。ルームやコミュニティを立ち上げて連絡を取り合い、物事をスムーズに成し遂げることができます。 + +いつでもどこでも!: すべてのデバイスとウェブ(https://app.element.io)でメッセージの履歴が完全に同期されるため、どこにいても連絡を取ることができます。 diff --git a/fastlane/metadata/android/ja/short_description.txt b/fastlane/metadata/android/ja/short_description.txt new file mode 100644 index 0000000000..c3991b7a93 --- /dev/null +++ b/fastlane/metadata/android/ja/short_description.txt @@ -0,0 +1 @@ +安全な分散型チャットとVoIP。あなたの情報が第三者から守られます。 diff --git a/fastlane/metadata/android/ja/title.txt b/fastlane/metadata/android/ja/title.txt new file mode 100644 index 0000000000..376f4a95de --- /dev/null +++ b/fastlane/metadata/android/ja/title.txt @@ -0,0 +1 @@ +Element(エレメントメッセンジャー) From 103e693c7eaac5695322d260a46a5ab1c35a8694 Mon Sep 17 00:00:00 2001 From: Thai Localization Date: Wed, 14 Apr 2021 07:00:26 +0000 Subject: [PATCH 057/230] Translated using Weblate (Thai) Currently translated at 12.6% (300 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/th/ --- vector/src/main/res/values-th/strings.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-th/strings.xml b/vector/src/main/res/values-th/strings.xml index a89dc740ac..1fbb9c70c3 100644 --- a/vector/src/main/res/values-th/strings.xml +++ b/vector/src/main/res/values-th/strings.xml @@ -153,7 +153,7 @@ เริ่มการประชุมวิดีโอ เริ่มแชท รีเซ็ต - ปิดทิ้ง + ปิด เลิกเชื่อมต่อ ดูต้นฉบับที่ถอดรหัสแล้ว ดูต้นฉบับ @@ -310,4 +310,14 @@ กำลังซิงค์… เอาออก รายการโปรด + รหัสผ่าน + เปลี่ยนรหัสผ่าน + เส้นเวลา + คุณได้ออกจากห้อง + %1$s ได้ออกจากห้อง + เพิกเฉย + เปลี่ยนชื่อห้อง + ห้ามผู้ใช้ + ลายนิ้วมือ (%s): + เพิกเฉยผู้ใช้ \ No newline at end of file From 14e80855b4200173c716bb731466df9622e2d58a Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 13 Apr 2021 10:46:38 +0000 Subject: [PATCH 058/230] Translated using Weblate (Swedish) Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv/changelogs/40101020.txt | 2 ++ fastlane/metadata/android/sv/changelogs/40101030.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/sv/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/sv/changelogs/40101030.txt diff --git a/fastlane/metadata/android/sv/changelogs/40101020.txt b/fastlane/metadata/android/sv/changelogs/40101020.txt new file mode 100644 index 0000000000..229793ab31 --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/sv/changelogs/40101030.txt b/fastlane/metadata/android/sv/changelogs/40101030.txt new file mode 100644 index 0000000000..7e0f8c80d2 --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.3 From ffb05ccc772a6e1c03b01a1c31e918ba1ef6b7df Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Tue, 13 Apr 2021 10:45:07 +0000 Subject: [PATCH 059/230] Translated using Weblate (Swedish) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 31ac8d13c6..c1be9f5678 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2672,4 +2672,5 @@ Skickad Skickar Rumskatalog + Meddelande skickat \ No newline at end of file From c2c8f929026efb46e043babc6fb2d342c886f35c Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Wed, 14 Apr 2021 16:02:23 +0000 Subject: [PATCH 060/230] Translated using Weblate (Japanese) Currently translated at 58.9% (1393 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- vector/src/main/res/values-ja/strings.xml | 414 ++++++++++++++++++++-- 1 file changed, 387 insertions(+), 27 deletions(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 13ef9801af..c1f95326cb 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -116,7 +116,7 @@ 会話 公開された部屋がありません - %d 名 + %d名 バグを報告 不具合の内容と状況の説明をお願い致します。あなたは何をしましたか?何が起こると思いますか?実際何が起こったのですか? @@ -355,7 +355,7 @@ 写真または動画を撮影 初期化メール送信 写真撮影やビデオ通話には, ${app_name}アプリに端末のカメラの使用を許可する必要があります. - 通話を開始できませんでした。後ほど試してください。 + 通話を開始できませんでした。後ほどお試しください 権限が無いため、一部の機能を利用できない可能性があります… この部屋で会議を開始するためには招待権限が必要です とにかく送る @@ -388,9 +388,9 @@ 電子メールアドレスまたは電話番号が違います 接続先サーバを指定する(追加設定) 指定が正しくありません - 電子メールと電話番号の同時登録は, まだシステムが対応できません. 電話番号だけの登録は可能です. - -お手数おかけしますが, 後ほど個人情報設定から電子メールアドレスを登録してください. + メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。 +\n +\n設定からプロフィールにメールアドレスを追加できます。 接続先サーバ側かが、機械による自動登録ではなく、あなたが人間であることを確認したいとのことです ユーザ名はすでに使用されています 接続先サーバ: @@ -401,9 +401,9 @@ 新しいパスワードの入力が必要です. %s へ電子メールが送信されました. リンクをたどったら以下をクリックしてください. 電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください - あなたのパスワードは初期化されました. - -あなたはすべての端末から切断しており、プッシュ通知を受け取ることはありません。通知を再度有効にするには、各端末に再度ログインします。 + パスワードがリセットされました。 +\n +\nすべてのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各デバイスで再ログインをお願いします。 登録ができません : 電子メールがあなた個人のものであるか確認できません 指定されたアクセストークンが認識されませんでした 不正な形式のJSON @@ -516,9 +516,9 @@ \n \n通話をするには、次のポップアップでアクセスできるように設定してください。" ${app_name}アプリは、音声通話を実行するためにマイクへアクセスするための許可が必要です。 - - -通話をするには、次のポップアップでアクセスできるようにしてください。 + " +\n +\n通話をするためには、次のポップアップでアクセスを許可してください。" ビデオ通話を行うには、カメラとマイクにアクセスするための権限が${app_name}アプリに必要です。 通話をするには、次のポップアップでのアクセスを許可してください。 @@ -674,11 +674,11 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 音声通話を開始して本当によろしいですか? 本当にビデオ通話を開始しますか? グループリスト - このユーザが対話に参加するのを本当にブロックしますか? + ユーザーを禁止するとこの部屋から追い出され、二度と参加できなくなります。 すべてのメッセージ (大音量) すべてのメッセージ ミュート - ホーム画面へのショートカットを作成 + ホーム画面にショートカットを作成 インラインURLプレビュー 通知 この部屋はコミュニティの特色を表示していません @@ -714,13 +714,13 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 再参加 バグを報告するには端末を降ってください - %d権限の変更 + %dメンバーシップの変更 参加者を表示 見出しを開く 同期中… - %d名のオンラインの参加者 + %d名の参加者 %d名 @@ -729,7 +729,7 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 %d件の新しいメッセージ - %d 部屋 + %d部屋 %2$s に %1$s 部屋見つかりました @@ -753,26 +753,26 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 可能であれば、英語で説明を書いてください。 音声を送信 スタンプを送信 - 現在使用可能なスタンプパックがありません。 - -今いくつか追加しますか? - …で続行 + 使用可能なスタンプパックがありません。 +\n +\n追加しますか? + 続行する… 申し訳ありません、この操作を完了するための外部アプリが見つかりません。 他のデバイスから 暗号鍵を再度要求 します。 鍵のリクエストが送信されました。 リクエスト送信済 鍵をこのデバイスに送信できるように、メッセージを復号化できる他のデバイスで${app_name}を起動してください。 - %d 秒 + %d秒 - %d 分 + %d分 - %d 時間 + %d時間 - %d 日 + %d日 現在 %1$s %2$s 前 %1$s @@ -782,7 +782,7 @@ ${app_name}アプリがあなたの電話帳へアクセスすることを許可 暗号化された返信を送信… 返信を送信 (未暗号化)… - %d 個選択済 + %d個選択済 低プライバシー • 通知中のメッセージの内容は Matrixのホームサーバから直接安全に入手しています @@ -903,7 +903,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 音声 ビデオ 詳細な通知設定 - バックグラウンド同期モード (実験) + バックグラウンド同期モード バッテリーを考慮して最適化 リアルタイム性を重視して最適化 バックグラウンド同期を行わない @@ -1142,7 +1142,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 通話の開始前に確認する 意図しない通話を阻止する お使いの端末は脆弱性のある古いTLSセキュリティプロトコルを使用しています、このセキュリティでは接続できません - SSLエラー + SSLエラー。 SSLエラー:相手のアイデンティティが認証されていません。 このURLからホームサーバーに接続できませんでした、ご確認ください 有効なMatrixサーバーアドレスではありません @@ -1196,4 +1196,364 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 名前または ID で検索 既知のユーザー 無効な QR コード (無効な URI)! + パスワードが一致しません + メールアドレスの確認中にエラーが発生しました。 + これを行うには設定からインテグレーションを許可を有効にしてください。 + インテグレーションが無効になっています + インテグレーションマネージャー(統合管理) + インテグレーションを許可 + データセーブモードでは、特定のフィルターを適用することで、プレゼンスの更新やタイピングの通知がフィルタリングされます。 + FCMトークンが正常に取得されました: +\n%1$s + Firebaseトークン + Playサービスを修正する + GooglePlayサービスAPKは利用可能で最新の状態になっています。 + Playサービスチェック + 設定を確認する + カスタムルールの読み込みに失敗しました。再試行してください。 + 一部の通知はカスタム設定で無効になっています。 + 一部のメッセージがサイレントに設定されていることに注意してください(音を出さずに通知します)。 + 1つ以上のテストが失敗しました。調査に役立つバグレポートを送信してください。 + 1つ以上のテストが失敗しました。提案された修正を試してください。 + 基本的な診断はOKです。 それでも通知が届かない場合は、調査に役立つバグレポートを送信してください。 + 実行中... (%1$dの %2$d) + テストを実行する + 診断トラブルシューティング + イベントごとの通知の優先順位 + メールであなたに送ったリンクをクリックして確認してください。 + %sを削除しますか? + 認証が必要です + あなたのパスワードを確認する + 暗号化された部屋での検索はまだサポートされていません。 + 禁止されたユーザーをフィルタリングする + トピックを変更する + ルームをアップグレード + m.room.server_acl eventsを送信します + 権限を変更する + 部屋名を変更する + 履歴を見えるように変更する + 部屋の暗号化を有効にする + 部屋のメインアドレスを変更する + 部屋のアバターを変更する + ウィジェットを変更する + 全員に通知する + 他の人から送信されたメッセージを削除する + 禁止ユーザー + キックユーザー + 設定を変更する + 招待されたユーザー + メッセージを送る + デフォルトルール + 部屋のさまざまな部分を変更するための必要な役割を更新する権限がありません + 部屋のさまざまな部分を変更するために必要な役割を選択します + 部屋の権限 + 権限 + 部屋のさまざまな部分を変更するために必要な役割を表示し更新します。 + ユーザーの禁止を解除すると、ユーザーは再び部屋に参加できるようになります。 + 禁止されたユーザー + 禁止の理由 + ユーザーの禁止を解除する + キックするユーザーは、この部屋から削除されます。 +\n +\n再び参加するのを防ぐためには永久追放する必要があります。 + キックする理由 + ユーザーをキックする + このユーザーの招待をキャンセルしてよろしいですか? + 招待をキャンセル + このユーザーを解除すると、そのユーザーからのすべてのメッセージが再び表示されます。 + ユーザーを無視しない + このユーザーを無視すると、あなたが共有している部屋からそのユーザーのメッセージが削除されます。 +\n +\nこの動作は設定からいつでも元に戻すことができます。 + ユーザーを無視する + 広角 + あなたは自分自身を降格させているので、この変更を元に戻すことはできません。あなたが部屋の中で最後の特権ユーザーである場合、特権を取り戻すことはできません。 + 降格しますか? + 招待をキャンセル + この部屋は公開されていません。 招待なしで再参加することはできません。 + このアクションを実行するには、設定にIDサーバーを追加します。 + 連絡先へのアクセスを許可します。 + QRコードをスキャンするには、カメラへのアクセスを許可する必要があります。 + 通話をかけました + %sが通話をかけました + かける + コールし直す + 通話をやり直す + アクティブな通話(%s) + ホームサーバーにアシスト機能を提供しない場合は、代わりに%sを使用します(IPアドレスは通話中に共有されます) + ビデオ通話が行われています… + フォールバックコールアシストサーバーを許可する + 有効な認証情報ではありません + ${app_name} 呼び出し失敗 + 通話が確実に機能させるためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。 +\n +\n代わりに、%2$sのパブリックサーバーを使用することもできますが、信頼性は低く、あなたのIPアドレスがそのサーバーと共有されてしまいます。これは設定から管理することができます。 + ルームディレクトリのすべての部屋を詳しい説明を含めて表示します。 + 部屋の詳しい説明を表示する + ルームディレクトリ + 新着情報 + 戻る + 非公開 + 切り替える + 加える + %1$sはエンドツーエンド暗号化 (認識されていないアルゴリズム%2$s) をオンにしました。 + エンドツーエンド暗号化 (認識されていないアルゴリズム%1$s) をオンにしました。 + 会話を始める + %1$sがエンドツーエンド暗号化をオンにしました。 + エンドツーエンド暗号化をオンにしました。 + ゲストが部屋に入るのを防いでいます。 + %1$sはゲストが部屋に参加するのを妨げました。 + ゲストが部屋に参加するのを妨ぎました。 + %1$sはゲストが部屋に参加するのを妨ぎました。 + ここへのゲストの入室を許可しました。 + %1$sはここにゲストが参加することを許可しました。 + この部屋へのゲストの入室を許可しました。 + %1$s がゲストの部屋への参加を許可しました。 + システムデフォルト + この部屋のメインおよび代替のアドレスを変更しました。 + この部屋の代替アドレスを変更しました。 + + この部屋の代替アドレス%1$sを削除しました。 + + + この部屋の代替アドレス%1$sを追加しました。 + + この部屋のメインアドレスを削除しました。 + この部屋のメイン・アドレスを%1$sに設定しました。 + この部屋のアドレスとして、%1$sを追加し%2$sを削除しました。 + + この部屋のアドレスの%1$sを削除しました。 + + %1$sの招待を取り消しました。理由:%2$s + %1$sの招待を受諾しました%2$。理由:%2$s + %1$sの部屋への招待を取り消しました。理由:%2$s + %1$sにルームへの招待状を送りました。理由:%2$s + VoIPカンファレンスをリクエストしました + この部屋のサーバのACLを変更しました。 + この部屋のサーバACLを設定しました。 + ここをアップグレードしました。 + この部屋をアップグレードしました。 + 通話を終えました。 + 通話に応えました。 + 通話を設定するためのデータを送信しました。 + この部屋のアドレスを変更しました。 + %1$s はこの部屋のメインおよび代替アドレスを変更しました。 + %1$s がこの部屋のアドレスを変更しました。 + %1$sはこの部屋の代替アドレスを変更しました。 + + %1$s がこの部屋の代替アドレス%2$sを削除しました。 + + + %1$s はこの部屋の代替アドレス%2$sを追加しました。 + + %1$sがこの部屋のメインアドレスを削除しました。 + %1$sがこの部屋のメインアドレスを%2$sに設定しました。 + %1$sはこの部屋のアドレスとして %2$sを追加し%3$sを削除しました。 + + %1$s はこの部屋のアドレスの%2$sを削除しました。 + + + この部屋のアドレスとして%1$sが追加されました。 + + %1$sが%2$s にルームへの招待を送りました。理由:%3$s + %1$sが%2$sの部屋への招待を取り消しました。理由:%3$s + %1$sが %2$sの招待を承諾しました。理由:%3$s + %1$sは%2$sの招待を取り下げました。理由:%3$s + + %1$sはこの部屋のアドレスとして%2$sを追加しました。 + + あなたのプロフィールが更新されました %1$s + %sがこの部屋のサーバのACLを変更しました。 + ・サーバーにマッチするIPリテラルを禁止されています。 + ・サーバーにマッチするIPリテラルを許可されています。 + ・%sに一致するサーバは許可されています。 + ・%sに一致するサーバは禁止されています。 + %sがこの部屋のサーバACLを設定しました。 + %sがここをアップグレードしました。 + %sがこの部屋をアップグレードしました。 + エンドツーエンド暗号化をオンにしました (%1$s) + 将来の部屋のメッセージを %1$sに見えるようにしました + 将来の部屋の履歴を %1$sに見えるようにしました + %1$s は将来のメッセージを %2$sに見えるようにしました + %sが通話を設定するためのデータを送信しました。 + 通話をしました。 + ビデオ通話をしました。 + あなたが%1$sを永久追放しました。理由: %2$s + %1$sが%2$sを永久追放しました。理由: %3$s + あなたが%1$sの禁止を解除しました。理由: %2$s + %1$sが%2$sの禁止を解除しました。理由: %3$s + あなたは%1$sをキックしました。理由: %2$s + %1$sが%2$sをキックしました。理由: %3$s + あなたは招待を拒否しました。理由: %1$s + %1$s は招待を拒否しました。理由: %2$s + 退出しました。理由: %1$s + %1$sが退出しました。理由: %2$s + あなたがこの部屋を退出しました。理由: %1$s + 初期同期: +\n退出した部屋のインポート + 初期同期: +\n招待された部屋のインポート + 初期同期: +\n参加した部屋のインポート + %1$sがこの部屋を退出しました。理由: %2$s + あなたがこの部屋に参加しました。理由: %1$s + %1$sがこの部屋に参加しました。理由: %2$s + あなたがこの部屋に参加しました。理由: %1$s + %1$sがこの部屋に参加しました。理由: %2$s + %1$sがあなたを招待しました。 理由: %2$s + あなたが%1$sを招待しました。 理由: %2$s + %1$sが%2$sを招待しました。 理由: %3$s + あなたの招待です。理由: %1$s + %1$sの招待です。理由: %2$s + 送信キューのクリア + メッセージを送っています… + メッセージを送る + 初期同期: +\nアカウントデータをインポート + 初期同期: +\nコミュニティをインポート + 初期同期: +\n部屋をインポート + 初期同期: +\n暗号をインポート + 初期同期: +\nアカウントをインポートしています… + 初期同期: +\nデータをダウンロードしています… + 初期同期: +\nサーバーからの応答を待っています… + 空の部屋(%sでした) + + %1$sと%2$sと%3$sそれに%4$dその他 + + %1$sと%2$sと%3$sそれに%4$s + %1$sと%2$s それに%3$s + %1$sを%2$sから%3$sへ + %2$sが%1$sの権限を変更しました。 + %1$sの権限を変更しました。 + カスタム + カスタム (%1$d) + デフォルト + モデレーター + 管理者 + あなたが %1$sがビデオ会議を変更しました + %1$sがビデオ会議を変更しました + ビデオ会議を開始しました + ビデオ会議を終了しました + %1$sがビデオ会議を開始しました + %1$sがビデオ会議を開始しました + あなたは%1$sウィジェットを変更しました + %1$sは%2$sウィジェットを変更しました + あなたは%1$sウィジェットを削除しました + %1$sが %2$sウィジェットを削除しました + あなたは%1$sウィジェットを追加しました + あなたは %1$sの招待を受けました + %1$sが %2$sウィジェットを追加しました + 部屋名を変更しました:%1$s + あなたは%1$sの招待を取り消しました + %1$sが%2$sの招待を取り消しました + あなたは%1$sを招待しました + あなたは%1$sの部屋への招待を取り消しました + %1$sが%2$sの部屋への招待を取り消しました + %1$sが%2$sを招待しました + あなたは%1$sに部屋への招待を送りました + メッセージが%1$sによって削除されました [理由: %2$s] + メッセージが削除されました [理由: %1$s] + %1$sがメッセージを削除しました + メッセージを削除 + 部屋のアバターを削除しました + %1$s が部屋のアバターを削除しました + 部屋のトピックを削除しました + 部屋名を削除しました + 許可を与える + ${app_name}は、信頼できる通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。 +\n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められます。同意してください。 + バックグラウンド接続 + ディスカバリー設定を管理します。 + ディスカバリー(発見) + これにより、現在のキーまたはフレーズが置き換えられます。 + 新しいセキュリティキーを生成するか、既存のバックアップに新しいセキュリティフレーズを設定します。 + サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。 + メッセージ作成画面に絵文字キーボードを開くボタンを追加する + 絵文字キーボードを表示する + アバターと表示名の変更が含まれます。 + アカウントイベントを表示する + 招待、キック、禁止は影響を受けません。 + 招待/入室/退室/キック/禁止イベントや、アバター/表示名の変更など。 + 入室と退室イベントの表示 + Use /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信します + チャットでエフェクトを表示する + 部屋のメンバーのイベントを表示する + ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。 + インテグレーションマネージャーを使用して、ボット、ブリッジ、ウィジェット、およびステッカーパックの管理をします。 +\nインテグレーションマネージャーは、構成データを受信し、ウィジェットを変更したり、部屋の招待状を送信したり、ユーザーに代わって権限を設定したりできます。 + 設定の更新に失敗しました。 + インテグレーション(統合) + アプリがバックグラウンドにある場合、着信メッセージは通知されません。 + ${app_name}は正確な時間に定期的にバックグラウンドで同期します(構成可能)。 +\nこれはラジオとバッテリーの使用量に影響し$ {app_name}がイベントをリッスンしていることを示す永続的な通知が表示されます。 + ${app_name}は、デバイスの限られたリソース(バッテリー)を維持する方法でバックグラウンド同期をします。 +\nデバイスの状態によっては、OSによって同期が延期される場合があります。 + LEDの色、振動、音を選択してください… + サイレント通知を構成する + 通話の通知を設定する + うるさい通知を設定する + アプリはバックグラウンドでホームサーバーに接続する必要がないためバッテリー使用量を減らすことができます + 最適化を無視する + 画面をオフにした状態でデバイスのプラグを抜いて一定時間静止したままにすると、デバイスは機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。 + ${app_name}に対してバックグラウンド制限が有効になっています。 +\nアプリがバックグラウンドで実行しようとすると積極的に制限され、通知に影響を与える可能性があります。 +\n%1$s + ${app_name}のバックグラウンド制限は無効になっています。 このテストは、モバイル(WIFIでない)を使用して実行する必要があります。 +\n%1$s + デバイスを再起動してもサービスは開始されません。${app_name}が一度開かれるまで通知は届きません。 + %1$s +\nこのエラーは${app_name}の管理外です。 これはいくつかの理由で発生する可能性があります。 後で再試行すればうまくいくかもしれません。システム設定でGoogle Play Serviceのデータ使用量が制限されていないか、デバイスの時刻が正しいかどうか、もしくはカスタムROMで起こることがあります。 + ${app_name}モバイルからこれを行うことはできません + ${app_name}はバッテリー最適化の影響を受けません。 + 制限を無効にする + 起動時の開始を有効にする + デバイスを再起動するとサービスが開始されます。 + サービスの再起動に失敗しました + サービスが強制終了され、自動的に再起動されました。 + 通知サービスの自動再起動 + 開始する + 通知サービスが実行されていません。 +\nアプリケーションを再起動してみてください。 + 通知サービスが機能しています。 + 通知サービス + 通知がクリックされました! + 通知をクリックしてください。 通知が表示されない場合は、システム設定を確認してください。 + 通知を表示 + 通知を表示しています。 クリックしてください! + プッシュの受信に失敗しました。 解決策は、アプリケーションを再インストールすることです。 + アプリケーションはプッシュを受信しています + アプリケーションはプッシュを待っています + テストプッシュ + FCMトークンのホームサーバーへの登録に失敗しました。 +\n%1$s + FCMトークンがホームサーバーに正常に登録されました。 + トークンの登録 + アカウントを追加する + [%1$s] +\nこのエラーは、${app_name}の管理外です。スマホにはGoogleアカウントがありません。アカウントマネージャーを開いて、Googleアカウントを追加してください。 + %1$s +\nこのエラーは${app_name}の管理外であり、Googleによると、このエラーは、デバイスにFCMに登録されているアプリが多すぎることを示しています。 このエラーは、アプリの数が極端に多い場合にのみ発生するため、平均的なユーザーには影響しません。 + ${app_name}はGoogle Playサービスを使用してプッシュメッセージを配信していますが、正しく設定されていないようです: +\n%1$s + FCMトークンの取得に失敗しました: +\n%1$s + 🎉すべてのサーバーの参加を禁止されています!この部屋は使用できなくなりました。 + 変化がありません。 + • サーバーにマッチするIPリテラルが禁止されるようになりました。 + • サーバーにマッチするIPリテラルが許可されるようになりました。 + • %sに一致するサーバーが許可リストから削除されました。 + • %sに一致するサーバーが許可されるようになりました。 + • %sに一致するサーバーが禁止リストから削除されました。 + • %sに一致するサーバーは禁止されました。 + + %1$sや %2$sそれに%3$dに他の人も読みました + + %1$sや %2$sそれに%3$sが読みました + メッセージをマークダウンとして解釈せずにプレーンテキストとして送信する \ No newline at end of file From 3b256dd8a0034ddaf5758ec436d32db02af9dd18 Mon Sep 17 00:00:00 2001 From: Moo Date: Tue, 13 Apr 2021 20:04:13 +0000 Subject: [PATCH 061/230] Translated using Weblate (Lithuanian) Currently translated at 1.3% (32 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lt/ --- vector/src/main/res/values-lt/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-lt/strings.xml b/vector/src/main/res/values-lt/strings.xml index db63c9385a..6eb730da63 100644 --- a/vector/src/main/res/values-lt/strings.xml +++ b/vector/src/main/res/values-lt/strings.xml @@ -3,12 +3,12 @@ %1$s: %2$s %1$s išsiuntė vaizdą. %1$s išsiuntė lipduką. - %s pakvietimas + Naudotojo %s pakvietimas Jūs prisijungėte prie kambario %1$s prisijungė prie kambario %1$s pakvietė jus - Jūs pakvietėte %1$s - %1$s pakvietė %2$s + Jūs pakvietėte naudotoją %1$s + %1$s pakvietė naudotoją %2$s Jūs sukūrėte diskusiją %1$s sukūrė diskusiją Jūs sukūrėte kambarį @@ -17,13 +17,13 @@ Jūs išsiuntėte lipduką. Jūs išsiuntėte vaizdą. Jūs atšaukėte %1$s pakvietimą - %1$s atšaukė %2$s pakvietimą + %1$s atšaukė naudotojo %2$s pakvietimą Jūs užblokavote %1$s - %1$s užblokavo %2$s + %1$s užblokavo naudotoją %2$s Jūs atblokavote %1$s - %1$s atblokavo %2$s + %1$s atblokavo naudotoją %2$s Jūs išmetėte %1$s - %1$s išmetė %2$s + %1$s išmetė naudotoją %2$s Jūs atmetėte pakvietimą %1$s atmetė pakvietimą Jūs išėjote iš kambario From c4401d2927b770a10460ec1086afa8c4589ac99e Mon Sep 17 00:00:00 2001 From: Line Date: Wed, 14 Apr 2021 22:20:42 +0000 Subject: [PATCH 062/230] Translated using Weblate (Latvian) Currently translated at 81.5% (1928 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/ --- vector/src/main/res/values-lv/strings.xml | 379 ++++++++++++++++++++++ 1 file changed, 379 insertions(+) diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index ee98765a7c..032c3d4d39 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -1773,4 +1773,383 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. \nLejupielādē datus… Sākotnējā sinhronizācija: \nGaida servera atbildi… + Pievienojiet speciālu cilni priekš nelasītiem paziņojumiem galvenajā ekrānā. + Ieslēgt pavilkšanas žestu, lai atbildētu uz ziņu + Netika atrasti rediģējumi + Rediģētas Ziņas + Rādīt pilnīgu vēsturi šifrētajās istabās + Importējiet E2E atslēgas no faila \"%1$s\". + Kļūda radās vācot atslēgu dublējumu datus + Kļūda radās vācot uzticības informāciju + Mainīt tīklu + Nav tīkla. Lūdzu, pārbaudiet savu interneta savienojumu. + Malformēts notikums, nevar parādīt + Notikumu pārvalda administrators + Lietotājs izdzēsa notikumu + Rādīt vietu priekš izdzēstām ziņām + Rādīt izdzēstās ziņas + Apskatīt Reakcijas + Pievienot Reakciju + Patīk + Piekrist + Jūsu sarunas tiks parādītas šeit. Nospiediet + ekrāna labējā, apakšējā stūrī, lai sāktu sarunas. + Apskatiet nelasītas ziņas šeit + Laipni lūgti mājās! + Viss panākts! + Jūs ielūdza %s + Pievienojieties istabai, lai sāktu lietot lietotni. + Nezināma Kļūda + Lietotāju neatbilstība + Atslēgu neatbilstība + Tika saņemta nederīga ziņa + Sesija saņēma negaidītu ziņu + SAS nav vienādi + Atslēgu Verifikācija + Izmantot veco verifikāciju. + Pārbaudiet šo sesiju, apstiprinot skaitļus, kas parādās uz ekrāniem + Pārbaudiet šo sesiju, apstiprinot Emoji, kas parādās uz ekrāniem + Pārbaudiet šo sesiju, lai atzīmētu to kā uzticamu. Uzticēšanās sesijai palīdz saziņas biedriem uzturēt mierīgu prātu, izmantojot E2E šifrētus ziņojumus. + Apstiprinot šo sesiju atzīmēs to kā uzticamu, un arī atzīmēs to kā uzticamu jūsu saziņas biedram. + Ienākošs verifikācijas pieprasījums + Sākt verificēt + Lai nodrošinātu maksimālu drošību, mēs iesakām to darīt personīgi vai izmantot citus uzticamus saziņas līdzekļus. + Apstipriniet, salīdzinot īsu teksta virkni. + Jūs tikāt izrakstīts, dēļ nepareiziem vai novecojušiem datiem. + Izmantot konfigurāciju + Paraksts + Visas atslēgas dublētas + Iestatiet Drošo Dublēšanu + Dublē jūsu atslēgas. Tas var aizņemt vairākas minūtes… + Pārvaldīt Atslēgu Dublējumā + Jauna šifrēto ziņu atslēga + Izmantot Atslēgu Dublēšanu + Nekad nezaudējiet šifrētās ziņas + Sāciet lietot Atslēgu Dublēšanu + Nekad nezaudējiet šifrētās ziņas + Ir konstatēts jauns šifrēto ziņu atslēgu dublējums. +\n +\nJa neesat iestatījis jauno atgūšanas metodi, uzbrucējs var mēģināt piekļūt jūsu kontam. Nomainiet savu konta paroli un nekavējoties iestatiet jaunu konta atkopšanas metodi Iestatījumos. + Jauns Atslēgu Dublējums + Dzēst savu dublēto šifrēšanas atslēgu no servera\? Jūs vairs nevarēsiet izmantot atkopšanas atsļēgu, lai lasītu šifrēto ziņu vēsturi. + Izdzēst dublējumu + Pārbauda dublējuma statusu + Neizdevās izdzēst dublējumu (%s) + Izdzēš dublējumu… + Izdzēst Dublējumu + Dublējums Atgūts %s ! + Atbloķēt Vēsturi + Importē atslēgas… + Lejupielādē atslēgas… + Izmantojiet savu Atgūšanas Atslēgu, lai atbloķētu savu šifrēto ziņu vēsturi + izmantojiet savu atgūšanas atslēgu + Lietotāju ielūgšana, izmešana un aizliegšana tika neaizskarta. + Konfigurēt Skaļās Notifikācijas + • Notifikācijas satur meta un ziņu datus + • Notifikācijas satur tikai meta datus + Izslēgt ierobežojumus + Pārbaudīt fona ierobežojumus + Pakalpojums tika beigts un restartēts automātiski. + Notifikāciju Pakalpojuma Auto-Restartēšana + Notifikāciju Pakalpojums nestrādā. +\nMēģiniet restartēt aplikāciju. + Notifikāciju Pakalpojums strādā. + Notifikācija tika nospiesta! + Neizdevās reģistrēt FCM žetonu serverī: +\n%1$s + FCM žetons veiksmīgi reģistrēts serverī. + FCM žetons veiksmīgi saņemts: +\n%1$s + FCM žetons netika saņemts: +\n%1$s + Žetona Reģistrācija + ${app_name} izmanto Google Play pakalpojumus, lai nodrošinātu ziņu pakalpojumu, bet tas nešķiet pareizi konfigurēts: +\n%1$s + Jums pašlaik nav iespējotas uzlīmes. +\n +\nPievienojiet dažas tagad\? + Neizdevās izveidot reālā laika savienojumu. +\nLūdzu, jautājiet sava servera administratoram, lai konfigurētu PAGRIEZIENA serveri, lai zvani strādātu uzticami. + Lūdzu, jautājiet jūsu servera administratoram (%1$s), lai konfigurētu PAGRIEZIENA serveri, lai zvani strādātu uzticami. +\n +\nAlternatīvi, jūs varat mēģināt izmantot publisko serveri %2$s, bet tas nebūs tik uzticams, un tas dalīs jūsu IP adresi ar šo serveri. To var arī pārvaldīt iestatījumos. + Izmantojiet Integrācijas Pārvaldnieku, lai pārvaldītu botus, tiltus, logrīkus un uzlīmes. +\nIntegrācijas Pārvaldnieks saņem konfigurācijas datus un var modificēt logrīkus, sūtīt istabu uzaicinājumus jūsu vārdā. + %s +\nSinhronizācija var tikt atlikta atkarībā no ierīces resursiem (akumulatora). + Vēlamais Sinhronizācijas Intervāls + Jūs nesaņemsiet jaunas notifikācijas, kad aplikācija ir fonā. + Bez fona sinhronizācijas + Optimizēts reālajam laikam + Fona Sinhronizācijas Režīms + Izvēlaites LED krāsu, vibrāciju, skaņu… + Konfigurēt Klusās Notifikācijas + Konfigurēt Zvanu Notifikācijas + Pakalpojums startēsies, kad ierīce būs restartēta. + Pakalpojumam neizdevās restartēties + Sākt Pakalpojumu + Notifikāciju Pakalpojums + Notifikāciju Displejs + Google Play Servisu APK ir pieejams un atjaunināts. + Filtrēt aizliegtos lietotājus + Lūdzu palaidiet ${app_name} citā ierīcē, kas var atšifrēt ziņu, lai tā varētu nosūtīt šīs sesijas atslēgas. + Neautorizēts, trūkstošas autentifikācijas apliecības + Atvainojiet, nav atrasta ārēja aplikācija, lai pabeigtu šo darbību. + turpināt ar… + ${app_name} Zvans Neizdevās + Nejautāt man atkal + Mēģiniet izmantot %s + Sūtīt atslēgu dalības vēsturi + Rādīt visas istabas direktorijā, tostarp istabas ar vecuma ierobežotu saturu. + Rādīt istabas ar vecuma ierobežojumu + 🎉 Visi serveri ir aizliegti piedalīties! Šo istabu vairs nevar izmantot. + Nekas nemainīts. + • Servers ar vienādu IP tagad ir aizliegts. + • Servers ar vienādu IP ir atļauts. + • Servers vienāds ar %s tika noņemts no atļautā saraksta. + • Servers vienāds ar %s tagad ir atļauts. + • Servers vienāds ar %s tika noņemts no aizliegtā saraksta. + • Servers vienāds ar %s tagad ir aizliegts. + Jūs nomainījāt servera ACL šai istabai. + %s nomainīja servera ACL šai istabai. + • Servers ar vienādu IP ir aizliegts. + • Servers ar vienādu IP ir atļauts. + • Servers vienāds ar %s ir atļauts. + • Servers vienāds ar %s ir aizliegts. + Jūs iestatījāt servera ACL šai istabai. + %s iestatīja servera ACL šai istabai. + Jūs atjauninājāt šeit. + %s atjaunināti šeit. + Sistēmas Brīdinājumi + Nepublicēt + Nolikt + Zvanīt Tāpat + Neizdevās noņemt logrīku + Neizdevās ielādēt logrīku + Startē pakalpojumu + Ziņa aizsūtīta + Jūs modificējāt video konferenci + %1$s modificēja video konferenci + Jūs beidzāt video konferenci + %1$s beidza video konferenci + Jūs sākāt video konferenci + %1$s sāka video konferenci + Jūs modificējāt %1$s logrīku + %1$s modificēja %2$s logrīku + Jūs noņēmāt %1$s logrīku + %1$s noņēma %2$s logrīku + Jūs pievienojāt %1$s logrīku + %1$s pievienoja %2$s logrīku + Izmantojiet savu atgūšanas frāzi, lai atbloķētu + Jūs varētu zaudēt piekļuvi savām ziņām, ja izrakstīsities vai pazaudēsiet šo ierīci. + Dublējums Sākts + Atgūšanas Atslēga + Lūdzu uztaisiet kopiju + Stop + Aizvietot + Dublējums jau pastāv jūsu serverī + Atgūšanas atslēgas tika saglabātas. + Atgūšanas atslēga ir saglabāta \"%s\". +\n +\nBRĪDINĀJUMS: Šis fails var tikt dzēsts, ja aplikācija ir atinstalēta. + Saglabāt kā Failu + Saglabāt Atgūšanas Atslēgu + Es uztaisīju kopiju + Saglabājiet savu atgūšanas atslēgu kaut kur ļoti drošā vietā, piemēram, paroles pārvaldniekā (vai seifā) + Jūsu atkopšanas atslēga ir drošības tīkls - to var izmantot, lai atjaunotu piekļuvi jūsu šifrētajām ziņām, ja esat aizmirsis savu paroles frāzi. +\nSaglabājiet savu atgūšanas atslēgu kaut kur ļoti drošā vietā, piemēram, paroles pārvaldniekā (vai seifā) + Jūsu atslēgas tiek dublētas. + Izdevās ! + (Advancēti) Iestatīt ar Atgūšanas Atslēgu + Vai, aizsargājiet jūsu dublējumu ar Atgūšanas Atslēgu, saglabājot to drošā vietā. + Taisa Dublējumu + Iestatīt Frāzi + Mēs saglabāsim šifrētu jūsu atslēgu kopiju savā serverī. Aizsargājiet savu dublējumu ar frāzi, lai tā būtu droši aizsargāta. +\n +\nLai nodrošinātu maksimālu drošību, tam jāatšķiras no jūsu konta paroles. + Aizsargājiet savu dublējumu ar Frāzi. + Manuāli eksportēt atslēgas + (Advancēti) + Sākt izmantot Atslēgu Dublēšanu + Ziņojumi šifrētās istabās ir nodrošināti ar E2E šifrēšanu. Tikai jums un saņēmējam (-iem) ir atslēgas, lai izlasītu šos ziņojumus. +\n +\nDroši dublējiet atslēgas, lai izvairītos no viņu zaudēšanas. + Nekad nepazaudējiet šifrētās ziņas + Nav pieejama Matrix sesija + Rādīt info laukumu + Palieliniet veiktspēju, ielādējot tikai tos lietotājus, kuri ir redzami. + Slinki ielādēt istabas lietotājus + Šis serveris ir sasniedzis savu ikmēneša aktīvo lietotāju limitu. + Šis serveris ir sasniedzis savu ikmēneša aktīvo lietotāju limitu, tāpēc daži lietotāji nevarēs pierakstīties . + Šis serveris ir pārsniedzis vienu no saviem resursu ierobežojumiem. + Šis servers ir pārsniedzis vienu no saviem resursu ierobežojumiem, tāpēc daži lietotāji nevarēs ierakstīties . + kontaktēt jūsu pakalpojuma administratoru + Kontaktēt Administratoru + Resursu Limits Pārsniegts + Lūdzu ievadiet paroli. + Lūdzu ievadiet lietotājvārdu. + Lūdzu aizmirst visas ziņas, ko esmu nosūtījis, kad mans konts ir deaktivizēts (Brīdinājums: šis nākotnes lietotājiem neļaus redzēt visu sarunu kontekstu) + Tas padarīs jūsu kontu nelietojamu. Jūs nevarēsiet ierakstīties, un neviens nevarēs atkārtoti reģistrēt to pašu lietotāja ID. Tas liks jūsu kontu atstāt visas istabas, kurās piedalās, un tas noņems jūsu konta datus no jūsu identitātes servera. Šī darbība ir neatgriezeniska . +\n +\nDeaktivizējot savu kontu , pēc noklusējuma, neliek mums aizmirst jūsu nosūtītās ziņas . Ja jūs vēlētos, lai mēs aizmirstu jūsu ziņas, lūdzu, atzīmējiet zemāk redzamo lodziņu. +\n +\nZiņu redzamība Matrix ir līdzīga e-pastam. Mums aizmirst jūsu ziņas nozīmē, ka ziņas, ko esat nosūtījis, netiks rādītas jauniem vai nereģistrētiem lietotājiem, bet reģistrētiem lietotājiem, kuriem jau ir piekļuve šīm ziņām, joprojām būs piekļuve to kopijai. + Atslēgas Dalīšanās Pieprasījums + Šij opcijai ir nepieciešama trešās partijas aplikācija, lai ierakstītu ziņas. + Nav aktīvu logrīku + Izmantot mikrofonu + Izmantot kameru + Bloķēt Visu + Atļaut + Šis logrīks vēlas izmantos tālāk norādītos resursus: + Iziet no pašreizējās konferences un pāriet uz citu\? + Atvainojiet, konferences zvani ar Jitsi netiek atbalstīti vecās ierīces (ierīces ar Android OS zem 6.0) + Istabas ID + Logrīka ID + Jūsu motīvs + Jūsu lietotāja ID + Jūsu avatāra URL + Atsaukt piekļuvi man + Atvērt pārlūkprogrammā + Pārlādēt logrīku + Neizdevās ielādēt logrīku. +\n%s + Šis logrīks bija pievienots no: + Ielādēt Logrīku + Logrīks + Aktīvie logrīki + Pārvaldīt Atslēgu Dublējumu + Šifrētu Ziņu Atgūšana + Publicēt šo istabu uz %1$s istabu sadaļu\? + Nepublicēt šo adresi + Publicēt šo adresi + Nevienu citu publicētu adrešu nav. + Nav publicētu adrešu pagaidām, pievienojat vienu apakšā. + Publicēt šo istabu uz %1$s istabu sadaļu\? + Nepublicēt adresi \"%1$s\"\? + Publicēt + Publicēt jaunu adresi manuāli + Šī ir galvenā adrese + Jūs pašlaik neesat kādas kopienas dalībnieks. + Lūdzu, ieslēdziet analītiku, lai palīdzētu mums uzlabot ${app_name}. + ${app_name} apkopo anonīmu analītiku, lai ļautu mums uzlabot aplikāciju. + Iedot atļauju + ${App_name} ir nepieciešams fona savienojums, lai spētu laicīgi piegādāt notifikācijas. +\nNākamajā ekrānā tiks piedāvāts atļaut ${app_name}, lai vienmēr darbotos fonā, lūdzu piekrītat. + Fona savienojums + Izvēlaties citu opciju + Dot atļauju + ${app_name} var darboties fonā, lai droši un privāti pārvaldītu jūsu paziņojumus. Tas var ietekmēt akumulatora izmantošanu. + Notifikāciju Privātums + Šis aizvietos jūsu esošo Atslēgu vai Frāzi. + Izveidot jaunu Drošības Atslēgu vai iestatīt jaunu Drošības Frāzi jūsu esošajam dublējumam. + Iestatīt uz šīs ierīces + Atiestatīt Drošo Dublējumu + Pārvaldīt + Pievienot pogu ziņu ievades laukā priekš Emoji + Rādīt Emoji tastatūru + Tastatūras Enter poga sūtīs ziņu, tā vietā lai pievienotu jaunu rindkopu + Sūtīt ziņu ar Enter + Iekļauj avatāra un vārda maiņas + Rādīt konta notikumus + Rādīt cilvēku pievienošanos vai iziešanu + Ietver uzaicinājumus/pievienošanos/aiziešanu/izmešanu/noteikumu pārkāpšanas/profila bildes izmaiņas. + Izmantojiet /konfeti komandu vai nosūtiet ziņu, kas satur ❄️ or 🎉 + Rādīt sarunu efektus + Rādīt istabas lietotāju vārda, profila bildes, utl. maiņu + Nospiediet izlasīšanas simbolu, priekš detalizētas informācijas. + Rādīt vai ziņa ir izlasīta/neizlasīta + Formatēt ziņas advancēti. Tas ļauj uzlabot formatējumu, piemēram, izmantojot zvaigznītes, lai parādītu slīprakstu tekstu. + Advancēts formatējums + Kriptogrāfijas Atslēgu Pārvaldība + Ļaut citiem lietotājiem zināt, ka jūs rakstāt. + Jūs skatāties ziņojumā! Klikšķiniet šeit! + Pievienot Kontu + Firebase Tokens + Salabot Play Servisus + Pārliecinieties, ka esat nospiedis uz saites e-pasta ziņojumā, ko esam nosūtījuši jums. + Optimizēts akumulatoram + ${App_name} sinhronizēsies fonā, tā lai ietaupītu telefona bateriju. +\nAtkarībā no ierīces baterijas stāvokļa, sinhronizāciju var atcelt operētājsistēma. + Aplikācijai vajag atļauju, lai strādātu fonā + Samazināts privātums + Ignorēt optimizāciju + ${App_name} neietekmē akumulatora optimizācija. + Akumulatora Optimizācija + Ieslēgt Aplikācijas palaišanu kopā ar telefonu + Play Servisu Pārbaude + Pārbaudiet Iestatījumus + Neizdevās ielādēt pielāgotos noteikumus, lūdzu, mēģiniet vēlreiz. + Daži paziņojumi ir izslēgti jūsu pielāgotos iestatījumos. + Ievērojiet, ka daži ziņojumi ir iestatīti kā klusi (paziņojumi pienāks bez skaņas). + Pielāgoti Iestatījumi. + Paziņojumi nav ieslēgti šai sesijai. +\nLūdzu, pārbaudiet ${app_name} iestatījumus. + Paziņojumi ir ieslēgti šai sesijai. + Sesijas Iestatījumi. + Paziņojumi ir atspējoti jūsu kontā. +\nLūdzu, pārbaudiet konta iestatījumus. + Paziņojumi ir ieslēgti jūsu kontam. + Konta iestatījumi. + Paziņojumi ir izslēgti sistēmas iestatījumos. +\nLūdzu, pārbaudiet sistēmas iestatījumus. + Paziņojumi ir ieslēgti sistēmas iestatījumos. + Sistēmas iestatījumi. + Viens vai vairāki testi nav izdevušies, lūdzu, iesniedziet kļūdu ziņojumu, lai palīdzētu mums izmeklēt. + Viens vai vairāki testi nav izdevušies, mēģiniet ieteiktos labojumus. + Pēc izskata ir labi. Ja jūs joprojām nesaņemat paziņojumus, lūdzu, iesniedziet kļūdu ziņojumu, lai palīdzētu mums izmeklēšanā. + Palaist Testus + Pārbaudes diagnostika + Paziņojumu pārbaude + Notifikāciju privātums + Paziņojumu svarīgums + Advancēti Notifikāciju Iestatījumi + Jūs apturējāt zvanu + %s apturēja zvanu + Apturēt + Turpināt + Izvēlieties zvana signālu: + Ienākoša zvana signāls + Izmantos %s kā rezervi, kad jūsu serveris nepiedāvā vienu (jūsu IP adrese tiks dalīta sarunas laikā) + Atļaut rezerves zvanu palīdzības serveri + Izmantot noklusējuma $ {app_name} zvana signālu ienākošajiem zvaniem + Pirms zvana uzsākšanas lūgt apstiprinājumu + Novērst nejaušu zvanu + Jūsu ierīce izmanto novecojušu TLS drošības protokolu, jūsu drošībai jūs nevarēsiet savienoties + SSL kļūda: Peer identitāte nav pārbaudīta. + SSL kļūda. + Šī URL serveris nav sasniedzams, lūdzu, pārbaudiet to + Šī nav derīga Matrix servera adrese + Šis URL nav sasniedzams, lūdzu, pārbaudiet to + Lūdzu, pārskatiet un pieņemiet šī servera noteikumus: + Iestatiet e-pastu, lai atgūtu kontu, aizmirstot paroli. Izmantojiet vēlāk e-pastu vai tālruni, lai Jums zināmi cilvēki varētu ar Jums sazināties. + Iestatiet e-pastu, lai atgūtu kontu, aizmirstot paroli. Izmantojiet vēlāk e-pastu vai tālruni, lai Jums zināmi cilvēki varētu ar Jums sazināties. + Iestatiet telefona numuru, lai Jums zināmi cilvēki varētu ar Jums sazināties. + Iestatiet e-pastu, lai atgūtu kontu aizmirstot paroli, vai lai Jums zināmi cilvēki varētu ar Jums sazināties. + Ieslēgt HD + Izslēgt HD + Priekšējā + Pārslēgt kameru + Izvēlieties Skaņas Ierīci + Sanāksmes izmanto Jitsi drošības un atļauju politiku. Visi cilvēki, kas pašlaik atrodas telpā, redzēs aicinājumu pievienoties, kamēr notiek jūsu sanāksme. + Sākt audio sanāksmi + Sākt video sanāksmi + Konference jau notiek! + Jums nav atļaujas sākt zvanu + Jums nav atļaujas sākt zvanu šajā istabā + Jums nav atļaujas sākt konferences zvanu + Trūkstošo atļauju dēļ, šī darbība nav iespējama. + Sākt Sarunu + Ja jūs nedublēsiet atslēgas pirms izrakstīšanās, zaudēsiet piekļuvi jūsu šifrētām ziņām. + Drošs Atslēgu Dublējums jābūt ieslēgts visās jūsu sesijās, lai izvairītos no piekļuves zaudēšanas jūsu šifrētām ziņām. + Dublēt + Izmantot Atslēgu Dublējumu + Dublē atslēgas… + Es nevēlos manas šifrētās ziņas + Notiek atslēgu dublējums. Ja jūs izrakstīsities tagad, jūs zaudēsiet piekļuvi jūsu šifrētām ziņām. + Jūs zaudēsiet savas šifrētās ziņas, ja izrakstīsities tagad + Atslēgu dublējums nav pabeigts, lūdzu uzgaidiet… + Izmantot Atslēgu Dublējumu + Atslēgu Dublējums + Jūs atjauninājāt šo istabu. + %s atjaunināja šo istabu. \ No newline at end of file From ea8b3ccdf664801bb46d62716a15c89c4a59abb3 Mon Sep 17 00:00:00 2001 From: Vivek K J Date: Wed, 14 Apr 2021 13:15:10 +0000 Subject: [PATCH 063/230] Translated using Weblate (Malayalam) Currently translated at 31.4% (744 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ml/ --- vector/src/main/res/values-ml/strings.xml | 115 ++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 2cef400e8e..f394245469 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -693,4 +693,119 @@ %1$s ഈ മുറിയുടെ പ്രധാന വിലാസം നീക്കംചെയ്‌തു. ഈ മുറിയുടെ പ്രധാന വിലാസം %1$s ആയി നിങ്ങൾ സജ്ജമാക്കി. സന്ദേശം അയച്ചു + കോൺടാക്റ്റുകൾ + പുതുതായത് + (എഡിറ്റുചെയ്തത്) + കാത്തിരിക്കുന്നു… + Format: + Url: + session_name: + app_display_name: + push_key: + app_id: + വിദഗ്ദ്ധൻ + മുൻ‌ഗണനകൾ + പൊതുവായ + പരസ്യമായത് + വിഷയം + പേര് + സൃഷ്ടിക്കുക + മുറികൾ + മാറ്റുക + പ്രതികരണങ്ങൾ + ലൈക്ക് + സമ്മതിക്കുക + പ്രതികരണങ്ങൾ + മുറികൾ + സംഭാഷണങ്ങൾ + വീണ്ടും ശ്രമിക്കുക + മറുപടി + തിരുത്തുക + പരിശോധിച്ചുറപ്പിച്ചു! + ഒപ്പ് + അൽഗോരിതം + പതിപ്പ് + നിർത്തുക + മാറ്റിസ്ഥാപിക്കുക + പങ്കിടുക + ചെയ്‌തു + (വിപുലമായത്) + %d+ + +%d + %1$s: + എല്ലായ്പ്പോഴും + സങ്കോചിപ്പിക്കുക + വിപുലീകരിക്കുക + അവതാർ + വീണ്ടും ചേരുക + ക്ഷണിച്ചു + ചേർന്നു + മുറികൾ + മുറികൾ + ആളുകൾ + പൂമുഖം + ഉദാഹരണം + ഉദാഹരണം + സൃഷ്ടിക്കൂ + ശബ്ദമേറിയത് + നിശബ്ദത + ഓഫ് + മുന്നറിയിപ്പ്! + അവഗണിക്കൂ + പങ്കിടുക + ഉറപ്പാക്കൂ + അനുവദിക്കുക + വിജറ്റ് + കാണുക + വൻ വലുപ്പമേറിയത് + ഏറ്റവും വലുത് + വളരെ വലുത് + വലുത് + സാധാരണ + ചെറുത് + തീരെച്ചെറുത് + ഞാൻ + മുറി + കരിമ്പട്ടിക + ഉറപ്പാക്കൂ + ഒന്നുമില്ല + കരിമ്പട്ടികയിൽ പെടുത്തി + പരിശോധിച്ചുറപ്പിച്ചു + സ്ഥിരീകരണം + അൽഗോരിതം + തീം + മേല്‍വിലാസപ്പട്ടിക + ലാബുകൾ + വിലാസങ്ങൾ + വിപുലമായവ + ആർക്കും + പ്രസിദ്ധീകരിക്കുക + അറിയിപ്പുകൾ + ഒന്നുമില്ല + പ്രിയപ്പെട്ടവ + എന്നേക്കും + തിരഞ്ഞെടുക്കുക + തിരഞ്ഞെടുക്കുക + മാധ്യമം + രഹസ്യവാക്ക് + രഹസ്യവാചകം: + പ്രാമാണീകരണം + ID + അനലിറ്റിക്സ് + കണ്ടെത്തൽ + നിയന്ത്രിക്കുക + ക്രിപ്റ്റോഗ്രഫി + സംയോജകങ്ങൾ + പകർപ്പാവകാശം + പതിപ്പ് + പ്രാപ്തമാക്കുക + പ്രാപ്തമാക്കുക + പകർപ്പാവകാശം + നിശബ്ദമാക്കുക + കഷണങ്ങൾ + ചേരുക + തിരയുക + അവഗണിക്കുക + നിരോധനം മാറ്റുക + സെഷനുകൾ \ No newline at end of file From c6a493848e87b623ccf36730cde23278e652c7e9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 16 Apr 2021 16:13:16 +0200 Subject: [PATCH 064/230] Fix a race condition: Push can be received before the Gateway API returns --- .../sdk/api/session/pushers/PushersService.kt | 5 +++-- .../troubleshoot/TestPushFromPushGateway.kt | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 9ea820f5b3..a5ec100f64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -66,12 +66,13 @@ interface PushersService { /** * Directly ask the push gateway to send a push to this device + * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. + * In case of error, PusherRejected will be thrown. In this case it means that the pushkey is not valid. + * * @param url the push gateway url (full path) * @param appId the application id * @param pushkey the FCM token * @param eventId the eventId which will be sent in the Push message. Use a fake eventId. - * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. - * In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid. */ suspend fun testPush(url: String, appId: String, diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt index ad4b9ecb01..15c7e88bac 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -42,8 +42,10 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) { private var action: Job? = null + private var pushReceived: Boolean = false override fun perform(activityResultLauncher: ActivityResultLauncher) { + pushReceived = false val fcmToken = FcmHelper.getFcmToken(context) ?: run { status = TestStatus.FAILED return @@ -55,9 +57,15 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat status = result .fold( { - // Wait for the push to be received - description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) - TestStatus.RUNNING + if (pushReceived) { + // Push already received (race condition) + description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_success) + TestStatus.SUCCESS + } else { + // Wait for the push to be received + description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) + TestStatus.RUNNING + } }, { description = if (it is PushGatewayFailure.PusherRejected) { @@ -73,6 +81,7 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat } override fun onPushReceived() { + pushReceived = true description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_success) status = TestStatus.SUCCESS } From 0903cb8bf15950a141b0e69ced30ed9ba5457a3c Mon Sep 17 00:00:00 2001 From: Soheil Khanalipur Date: Fri, 16 Apr 2021 10:21:52 +0000 Subject: [PATCH 065/230] Translated using Weblate (Persian) Currently translated at 100.0% (2363 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 7937f4f4b2..ee58a8e508 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -223,7 +223,7 @@ در حال گوش دادن به رویدادها پیام‌ها اتاق - تنظیمات + ساماندهی جزئیات اعضا گزارش اشکال در حال بارگذاری… @@ -394,11 +394,11 @@ تماس‌ها تماس ذخیره شد - بله - نه + بَلِہ + نَه در دانلودها ذخیره شود؟ ادامه - حذف + زُدودَن پیوستن پیش‌نمایش رد کردن @@ -449,7 +449,7 @@ ابزارهای مدیر تماس گپ‌های مستقیم - نشست‌ها + نِشَست‌ھا دعوت ترک این اتاق حذف از این اتاق @@ -545,7 +545,7 @@ تأیید گذرواژه‌تان نمی‌توانید با المنت همراه، این کار را بکنید نیاز به تأیید هویت است - تنظمیات پیش‌رفتهٔ آگاهی + ساماندھیِ پیشرَفتِہ‌یِ آگَہداد آگاهی‌های رفع‌اشکال آگاهی‌ها در تنظیمات سامانه به کار افتاده‌اند. آگاهی‌ّا در تنظیمات سامانه از کار افتاده‌اند. @@ -805,7 +805,7 @@ پریدن به نخستین پیام خوانده نشده. شما برای پیوستن به این اتاق توسط %s دعوت شدید یک اتاق - گپ جدید + گَپِ نو افزودن عضو %d عضو فعّال @@ -852,7 +852,7 @@ توقف رونوشت موفقیت - اعلان‌ها + آگَہداد خاتمه اجازهٔ شروع تماس کنفرانسی در این اتاق را ندارید اجازهٔ شروع تماس در این اتاق را ندارید @@ -913,10 +913,10 @@ دعوت کاربر با شناسه لطفاً یک یا چند نشانی رایانامه یا شناسهٔ ماتریکس را وارد کنید رایانامه یا شناسهٔ ماتریکس - جست‌وجو - %s دارد می‌نویسد… - %1$s و %2$s دارند می‌نویسند… - %1$s و %2$s و دیگران دارند می‌نویسند… + جُستُجو + %s دارد می‌نِویسَد… + %1$s و %2$s دارَند می‌نِویسَند… + %1$s و %2$s و دیگَران دارَند می‌نِویسَند… فرستادن پیام رمزشده… فرستادن پیام (رمز نشده)… اتّصال به کارساز از دست رفت. @@ -2672,4 +2672,5 @@ نمایش همه‌ی اتاق‌های داخل فهرست. نمایش‌ها همه‌ی اتاق‌ها فهرست اتاق‌ها + پَیام فِرِستاده شُد \ No newline at end of file From e3e0139d2be8803955adf698fd68affa7becca3e Mon Sep 17 00:00:00 2001 From: GokdenizK Date: Fri, 16 Apr 2021 10:18:58 +0000 Subject: [PATCH 066/230] Translated using Weblate (Turkish) Currently translated at 68.0% (1609 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/ --- vector/src/main/res/values-tr/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index 4739830ea4..3e80f9f496 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -1816,4 +1816,8 @@ Bu odayı geliştirdiniz. %s bu odayı geliştirdi. Uçtan uca şifrelemeyi açtınız (%1$s) + %1$s %2$s widgetını düzenledi + %1$s %2$s widgetını kaldırdı + %1$s uçtan uca şifrelemeyi etkinleştirdi (%2$s) + bilinmeyen (%s). \ No newline at end of file From f8be8140790757ee7b1ada14ac5175e108503e1b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 10:49:47 +0200 Subject: [PATCH 067/230] RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205) --- CHANGES.md | 2 +- .../android/sdk/api/auth/registration/RegistrationWizard.kt | 4 +++- .../internal/auth/registration/DefaultRegistrationWizard.kt | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 57582d884a..c9b155f7ad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,7 +14,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205) Build 🧱: - Upgrade to gradle 7 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt index 38a5a77291..f059bf26c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt @@ -20,7 +20,9 @@ interface RegistrationWizard { suspend fun getRegistrationFlow(): RegistrationResult - suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult + suspend fun createAccount(userName: String?, + password: String?, + initialDeviceDisplayName: String?): RegistrationResult suspend fun performReCaptcha(response: String): RegistrationResult diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 4a3d53a8fc..4a156e74cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -66,8 +66,8 @@ internal class DefaultRegistrationWizard( return performRegistrationRequest(params) } - override suspend fun createAccount(userName: String, - password: String, + override suspend fun createAccount(userName: String?, + password: String?, initialDeviceDisplayName: String?): RegistrationResult { val params = RegistrationParams( username = userName, From ef1ed28ac5f176664cf360363cd0ba88819fb816 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 16:04:59 +0200 Subject: [PATCH 068/230] Fix color issues when the system theme is changed (#2738) --- CHANGES.md | 1 + .../im/vector/app/features/configuration/VectorConfiguration.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 57582d884a..c188c1d145 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: Bugfix 🐛: - Message states cosmetic changes (#3007) + - Fix color issues when the system theme is changed (#2738) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt index 394eca030b..a2d190bd69 100644 --- a/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/configuration/VectorConfiguration.kt @@ -39,6 +39,8 @@ class VectorConfiguration @Inject constructor(private val context: Context) { Timber.v("## onConfigurationChanged(): restore the expected value ${VectorLocale.applicationLocale}") Locale.setDefault(VectorLocale.applicationLocale) } + // Night mode may have changed + ThemeUtils.init(context) } fun applyToApplicationContext() { From 5862c8cb607bac840cec094537c21d2b32fa8b1c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 17:08:17 +0200 Subject: [PATCH 069/230] Reduce usage of rxSingle when it's easy to do it --- .../java/org/matrix/android/sdk/rx/RxRoom.kt | 11 -------- .../org/matrix/android/sdk/rx/RxSession.kt | 11 -------- .../vector/app/core/mvrx/ResultExtension.kt | 26 +++++++++++++++++++ .../createdirect/CreateDirectRoomViewModel.kt | 16 +++++++----- .../home/room/detail/RoomDetailViewModel.kt | 16 +++++++----- 5 files changed, 46 insertions(+), 34 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt 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 21db4e1893..3f61ca0103 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 @@ -19,9 +19,7 @@ package org.matrix.android.sdk.rx import android.net.Uri import io.reactivex.Completable import io.reactivex.Observable -import io.reactivex.Single import kotlinx.coroutines.rx2.rxCompletable -import kotlinx.coroutines.rx2.rxSingle 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.identity.ThreePid @@ -91,15 +89,6 @@ class RxRoom(private val room: Room) { return room.getMyReadReceiptLive().asObservable() } - fun loadRoomMembersIfNeeded(): Single = rxSingle { - room.loadRoomMembersIfNeeded() - } - - fun joinRoom(reason: String? = null, - viaServers: List = emptyList()): Single = rxSingle { - room.join(reason, viaServers) - } - fun liveEventReadReceipts(eventId: String): Observable> { return room.getEventReadReceiptsLive(eventId).asObservable() } diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index 0fe2b01576..5d8b019284 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -124,22 +123,12 @@ class RxSession(private val session: Session) { .startWithCallable { session.getPendingThreePids() } } - fun createRoom(roomParams: CreateRoomParams): Single = rxSingle { - session.createRoom(roomParams) - } - fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set): Single> = rxSingle { session.searchUsersDirectory(search, limit, excludedUserIds) } - fun joinRoom(roomIdOrAlias: String, - reason: String? = null, - viaServers: List = emptyList()): Single = rxSingle { - session.joinRoom(roomIdOrAlias, reason, viaServers) - } - fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Single> = rxSingle { session.getRoomIdByAlias(roomAlias, searchOnServer) diff --git a/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt b/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt new file mode 100644 index 0000000000..1dbf658deb --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 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.mvrx + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Success + +fun Result.foldToAsync(): Async = fold( + { Success(it) }, + { Fail(it) } +) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index cbe363aa0e..0cc128f749 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -26,6 +26,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.mvrx.foldToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault @@ -35,7 +36,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.rx.rx class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, @@ -99,11 +99,15 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - session.rx() - .createRoom(roomParams) - .execute { - copy(createAndInviteState = it) - } + val result = runCatching { + session.createRoom(roomParams) + }.foldToAsync() + + setState { + copy( + createAndInviteState = result + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index f0732d8f07..725cce70ab 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -35,6 +35,7 @@ import dagger.assisted.AssistedInject import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.mvrx.foldToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.call.dialpad.DialPadLookup @@ -649,12 +650,15 @@ class RoomDetailViewModel @AssistedInject constructor( val viaServers = MatrixPatterns.extractServerNameFromId(action.event.senderId) ?.let { listOf(it) } .orEmpty() - session.rx() - .joinRoom(roomId, viaServers = viaServers) - .map { roomId } - .execute { - copy(tombstoneEventHandling = it) - } + viewModelScope.launch { + val result = runCatching { + session.joinRoom(roomId, viaServers = viaServers) + roomId + }.foldToAsync() + setState { + copy(tombstoneEventHandling = result) + } + } } } From f086f0e083a412532ed4eac6ef99bc9f18bcf73e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 17:13:48 +0200 Subject: [PATCH 070/230] Reduce usage of rxSingle when it's easy to do it - case 2 --- .../RoomMemberProfileViewModel.kt | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 64081a1683..0fa681a799 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -29,6 +29,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import im.vector.app.R +import im.vector.app.core.mvrx.foldToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.powerlevel.PowerLevelsObservableFactory @@ -139,7 +140,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v override fun handle(action: RoomMemberProfileAction) { when (action) { - is RoomMemberProfileAction.RetryFetchingInfo -> fetchProfileInfo() + is RoomMemberProfileAction.RetryFetchingInfo -> handleRetryFetchProfileInfo() is RoomMemberProfileAction.IgnoreUser -> handleIgnoreAction() is RoomMemberProfileAction.VerifyUser -> prepareVerification() is RoomMemberProfileAction.ShareRoomMemberProfile -> handleShareRoomMemberProfile() @@ -259,18 +260,27 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } } - private fun fetchProfileInfo() { - session.rx().getProfileInfo(initialState.userId) - .map { - MatrixItem.UserItem( - id = initialState.userId, - displayName = it[ProfileService.DISPLAY_NAME_KEY] as? String, - avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String - ) - } - .execute { - copy(userMatrixItem = it) - } + private fun handleRetryFetchProfileInfo() { + viewModelScope.launch { + fetchProfileInfo() + } + } + + private suspend fun fetchProfileInfo() { + val result = runCatching { + session.getProfile(initialState.userId) + .let { + MatrixItem.UserItem( + id = initialState.userId, + displayName = it[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String + ) + } + }.foldToAsync() + + setState { + copy(userMatrixItem = result) + } } private fun observeRoomSummaryAndPowerLevels(room: Room) { From 820531120a0e45c448002044c0e75570a6e1aa2c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 17:33:57 +0200 Subject: [PATCH 071/230] Some formatting and cleanup --- .../features/permalink/PermalinkHandler.kt | 5 ++++- .../userdirectory/UserListViewModel.kt | 22 +++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index a7d69c783c..ae6d630c75 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -112,7 +112,10 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti private fun PermalinkData.RoomLink.getRoomId(): Single> { val session = activeSessionHolder.getSafeActiveSession() return if (isRoomAlias && session != null) { - session.rx().getRoomIdByAlias(roomIdOrAlias, true).map { it.getOrNull()?.roomId.toOptional() }.subscribeOn(Schedulers.io()) + session.rx() + .getRoomIdByAlias(roomIdOrAlias, true) + .map { it.getOrNull()?.roomId.toOptional() } + .subscribeOn(Schedulers.io()) } else { Single.just(Optional.from(roomIdOrAlias)) } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 0e042c8bc1..a85b2cd95f 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -144,15 +144,19 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User } .onErrorReturn { Optional.empty() } - Single.zip(searchObservable, profileObservable, { searchResults, optionalProfile -> - val profile = optionalProfile.getOrNull() ?: return@zip searchResults - val searchContainsProfile = searchResults.indexOfFirst { it.userId == profile.userId } != -1 - if (searchContainsProfile) { - searchResults - } else { - listOf(profile) + searchResults - } - }) + Single.zip( + searchObservable, + profileObservable, + { searchResults, optionalProfile -> + val profile = optionalProfile.getOrNull() ?: return@zip searchResults + val searchContainsProfile = searchResults.any { it.userId == profile.userId } + if (searchContainsProfile) { + searchResults + } else { + listOf(profile) + searchResults + } + } + ) } } stream.toAsync { From 2a3694d8b72c80a1bec0720f27bd18ea9943bea3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 18:12:11 +0200 Subject: [PATCH 072/230] Cleanup --- .../vector/app/features/userdirectory/UserListViewModel.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index a85b2cd95f..77f6c3f4eb 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -29,7 +29,6 @@ import im.vector.app.core.extensions.toggle import im.vector.app.core.platform.VectorViewModel import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.profile.ProfileService @@ -50,8 +49,6 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private val knownUsersSearch = BehaviorRelay.create() private val directoryUsersSearch = BehaviorRelay.create() - private var currentUserSearchDisposable: Disposable? = null - @AssistedFactory interface Factory { fun create(initialState: UserListViewState): UserListViewModel @@ -115,8 +112,6 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User copy(knownUsers = async) } - currentUserSearchDisposable?.dispose() - directoryUsersSearch .debounce(300, TimeUnit.MILLISECONDS) .switchMapSingle { search -> From 5848ec21f709e6bea460f65e62371cddd858c607 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 18:16:03 +0200 Subject: [PATCH 073/230] use orEmpty() --- .../im/vector/app/features/invite/InviteUsersToRoomViewModel.kt | 2 +- .../vector/app/features/roomdirectory/RoomDirectoryViewModel.kt | 2 +- .../im/vector/app/features/userdirectory/UserListViewModel.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt index 55730ff4ec..2f20fef3c9 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomViewModel.kt @@ -92,6 +92,6 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted } fun getUserIdsOfRoomMembers(): Set { - return room.roomSummary()?.otherMemberIds?.toSet() ?: emptySet() + return room.roomSummary()?.otherMemberIds?.toSet().orEmpty() } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 7b55e38764..cfc8d9f099 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -87,7 +87,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( val joinedRoomIds = list ?.map { it.roomId } ?.toSet() - ?: emptySet() + .orEmpty() setState { copy(joinedRoomsIds = joinedRoomIds) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 77f6c3f4eb..3e1e06d47e 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -119,7 +119,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User Single.just(emptyList()) } else { val searchObservable = session.rx() - .searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet()) + .searchUsersDirectory(search, 50, state.excludedUserIds.orEmpty()) .map { users -> users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } } From b553d390e112b830d69747e46238ca3e6fda33d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 18:30:37 +0200 Subject: [PATCH 074/230] Remove unused code --- .../android/sdk/rx/RxCallbackBuilders.kt | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt 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 deleted file mode 100644 index ec30a31f6d..0000000000 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.rx - -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable -import io.reactivex.Completable -import io.reactivex.Single - -fun singleBuilder(builder: (MatrixCallback) -> Cancelable): Single = Single.create { emitter -> - val callback = object : MatrixCallback { - override fun onSuccess(data: T) { - // 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) { - emitter.tryOnError(failure) - } - } - val cancelable = builder(callback) - emitter.setCancellable { - cancelable.cancel() - } -} - -fun completableBuilder(builder: (MatrixCallback) -> Cancelable): Completable = Completable.create { emitter -> - val callback = object : MatrixCallback { - override fun onSuccess(data: T) { - emitter.onComplete() - } - - override fun onFailure(failure: Throwable) { - emitter.tryOnError(failure) - } - } - val cancelable = builder(callback) - emitter.setCancellable { - cancelable.cancel() - } -} From f4c1de3c30bb91fff1cc655cc70f3862f1191f77 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 18:49:16 +0200 Subject: [PATCH 075/230] Fix exception in rxSingle (#3180) Do not wrap CancellationException to Failure.Cancelled, else RxCancellable will throw. --- CHANGES.md | 1 + .../main/java/org/matrix/android/sdk/api/failure/Failure.kt | 1 - .../java/org/matrix/android/sdk/internal/network/Request.kt | 4 ++-- .../android/sdk/internal/session/sync/job/SyncThread.kt | 3 ++- .../app/features/home/room/detail/search/SearchViewModel.kt | 4 ++-- .../im/vector/app/features/login/AbstractLoginFragment.kt | 3 ++- .../app/features/roomdirectory/RoomDirectoryViewModel.kt | 4 ++-- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c9b155f7ad..f2c5fb5d11 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: Bugfix 🐛: - Message states cosmetic changes (#3007) + - Fix exception in rxSingle (#3180) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt index b241903364..8f1bbb6941 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt @@ -32,7 +32,6 @@ import java.io.IOException */ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { data class Unknown(val throwable: Throwable? = null) : Failure(throwable) - data class Cancelled(val throwable: Throwable? = null) : Failure(throwable) data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure() data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException) data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString())) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 0246bae024..e045cebd3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -88,8 +88,8 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr throw when (exception) { is IOException -> Failure.NetworkConnection(exception) is Failure.ServerError, - is Failure.OtherServerError -> exception - is CancellationException -> Failure.Cancelled(exception) + is Failure.OtherServerError, + is CancellationException -> exception else -> Failure.Unknown(exception) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 424c24663c..de8d009892 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import com.squareup.moshi.JsonEncodingException +import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.sync.SyncState @@ -199,7 +200,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) { // Timeout are not critical Timber.v("Timeout") - } else if (failure is Failure.Cancelled) { + } else if (failure is CancellationException) { Timber.v("Cancelled") } else if (failure.isTokenError()) { // No token or invalid token, stop the thread diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt index fb3abf002e..26111e4f2b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt @@ -28,9 +28,9 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.search.SearchResult @@ -120,7 +120,7 @@ class SearchViewModel @AssistedInject constructor( ) onSearchResultSuccess(result) } catch (failure: Throwable) { - if (failure is Failure.Cancelled) return@launch + if (failure is CancellationException) return@launch _viewEvents.post(SearchViewEvents.Failure(failure)) setState { diff --git a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt index e537f31e78..c16c7c6645 100644 --- a/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/AbstractLoginFragment.kt @@ -29,6 +29,7 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseFragment +import kotlinx.coroutines.CancellationException import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import javax.net.ssl.HttpsURLConnection @@ -76,7 +77,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment() } when (throwable) { - is Failure.Cancelled -> + is CancellationException -> /* Ignore this error, user has cancelled the action */ Unit is Failure.ServerError -> diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index cfc8d9f099..f64105b759 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -29,10 +29,10 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter @@ -183,7 +183,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( ) ) } catch (failure: Throwable) { - if (failure is Failure.Cancelled) { + if (failure is CancellationException) { // Ignore, another request should be already started return@launch } From ff3a916cabc991d8191525ebeb25b1a13371cc0f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 18:59:49 +0200 Subject: [PATCH 076/230] Do not invite the current user when creating a room (#3123) --- CHANGES.md | 1 + .../internal/session/room/create/CreateRoomBodyBuilder.kt | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f2c5fb5d11..94ceb85fbd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Improvements 🙌: Bugfix 🐛: - Message states cosmetic changes (#3007) - Fix exception in rxSingle (#3180) + - Do not invite the current user when creating a room (#3123) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 5e823fc87f..80be49de61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.di.AuthenticatedIdentity +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask @@ -43,6 +44,8 @@ internal class CreateRoomBodyBuilder @Inject constructor( private val deviceListManager: DeviceListManager, private val identityStore: IdentityStore, private val fileUploader: FileUploader, + @UserId + private val userId: String, @AuthenticatedIdentity private val accessTokenProvider: AccessTokenProvider ) { @@ -80,7 +83,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( roomAliasName = params.roomAliasName, name = params.name, topic = params.topic, - invitedUserIds = params.invitedUserIds, + invitedUserIds = params.invitedUserIds.filter { it != userId }, invite3pids = invite3pids, creationContent = params.creationContent.takeIf { it.isNotEmpty() }, initialStates = initialStates, From 19e07f6cac833167c4dae3d54ba4eccdac0ae028 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 19:06:32 +0200 Subject: [PATCH 077/230] typo --- .../im/vector/app/features/userdirectory/UserListFragment.kt | 2 +- .../im/vector/app/features/userdirectory/UserListViewEvents.kt | 2 +- .../im/vector/app/features/userdirectory/UserListViewModel.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index 4a87d13021..6e6df7a7aa 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -89,7 +89,7 @@ class UserListFragment @Inject constructor( viewModel.observeViewEvents { when (it) { - is UserListViewEvents.OpenShareMatrixToLing -> { + is UserListViewEvents.OpenShareMatrixToLink -> { val text = getString(R.string.invite_friends_text, it.link) startSharePlainTextIntent( fragment = this, diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt index 95c6729fad..f5ec044597 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt @@ -22,5 +22,5 @@ import im.vector.app.core.platform.VectorViewEvents * Transient events for invite users to room screen */ sealed class UserListViewEvents : VectorViewEvents { - data class OpenShareMatrixToLing(val link: String) : UserListViewEvents() + data class OpenShareMatrixToLink(val link: String) : UserListViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt index 3e1e06d47e..a68717ea57 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -89,7 +89,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User private fun handleShareMyMatrixToLink() { session.permalinkService().createPermalink(session.myUserId)?.let { - _viewEvents.post(UserListViewEvents.OpenShareMatrixToLing(it)) + _viewEvents.post(UserListViewEvents.OpenShareMatrixToLink(it)) } } From cf69d5c882fd3e5d95eb3dc8ddfff0ef4e19e87c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 19:11:37 +0200 Subject: [PATCH 078/230] Fix small bug about suggestion --- .../userdirectory/UserListController.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index a7ec9cd8c3..798f8b9345 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -135,7 +135,9 @@ class UserListController @Inject constructor(private val session: Session, } private fun buildDirectoryUsers(directoryUsers: List, selectedUsers: List, searchTerms: String, ignoreIds: List) { - val toDisplay = directoryUsers.filter { !ignoreIds.contains(it.userId) } + val toDisplay = directoryUsers + .filter { !ignoreIds.contains(it.userId) && it.userId != session.myUserId } + if (toDisplay.isEmpty() && searchTerms.isBlank()) { return } @@ -147,16 +149,14 @@ class UserListController @Inject constructor(private val session: Session, renderEmptyState() } else { toDisplay.forEach { user -> - if (user.userId != session.myUserId) { - val isSelected = selectedUsers.contains(user.userId) - userDirectoryUserItem { - id(user.userId) - selected(isSelected) - matrixItem(user.toMatrixItem()) - avatarRenderer(avatarRenderer) - clickListener { _ -> - callback?.onItemClick(user) - } + val isSelected = selectedUsers.contains(user.userId) + userDirectoryUserItem { + id(user.userId) + selected(isSelected) + matrixItem(user.toMatrixItem()) + avatarRenderer(avatarRenderer) + clickListener { _ -> + callback?.onItemClick(user) } } } From 7309fa547b46469fb1f7ccdb86152ad425d11668 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 20 Apr 2021 19:13:42 +0200 Subject: [PATCH 079/230] Exclude current user from the list of user to invite (#3123) --- .../userdirectory/UserListController.kt | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt index 798f8b9345..90fb828663 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -109,29 +109,31 @@ class UserListController @Inject constructor(private val session: Session, } private fun buildKnownUsers(currentState: UserListViewState, selectedUsers: List) { - currentState.knownUsers()?.let { userList -> - userListHeaderItem { - id("known_header") - header(stringProvider.getString(R.string.direct_room_user_list_known_title)) - } + currentState.knownUsers() + ?.filter { it.userId != session.myUserId } + ?.let { userList -> + userListHeaderItem { + id("known_header") + header(stringProvider.getString(R.string.direct_room_user_list_known_title)) + } - if (userList.isEmpty()) { - renderEmptyState() - return - } - userList.forEach { item -> - val isSelected = selectedUsers.contains(item.userId) - userDirectoryUserItem { - id(item.userId) - selected(isSelected) - matrixItem(item.toMatrixItem()) - avatarRenderer(avatarRenderer) - clickListener { _ -> - callback?.onItemClick(item) + if (userList.isEmpty()) { + renderEmptyState() + return + } + userList.forEach { item -> + val isSelected = selectedUsers.contains(item.userId) + userDirectoryUserItem { + id(item.userId) + selected(isSelected) + matrixItem(item.toMatrixItem()) + avatarRenderer(avatarRenderer) + clickListener { _ -> + callback?.onItemClick(item) + } + } } } - } - } } private fun buildDirectoryUsers(directoryUsers: List, selectedUsers: List, searchTerms: String, ignoreIds: List) { From bb48fea11631256301735d4f53591ddd7cdce110 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 17:59:41 +0000 Subject: [PATCH 080/230] Bump epoxy_version from 4.4.4 to 4.5.0 Bumps `epoxy_version` from 4.4.4 to 4.5.0. Updates `epoxy` from 4.4.4 to 4.5.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.4...4.5.0) Updates `epoxy-glide-preloading` from 4.4.4 to 4.5.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.4...4.5.0) Updates `epoxy-processor` from 4.4.4 to 4.5.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.4...4.5.0) Updates `epoxy-paging` from 4.4.4 to 4.5.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.4...4.5.0) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 3d30a2a2f6..b9f37f8767 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -290,7 +290,7 @@ android { dependencies { - def epoxy_version = '4.4.4' + def epoxy_version = '4.5.0' def fragment_version = '1.3.2' def arrow_version = "0.8.2" def markwon_version = '4.1.2' From 7575959c4a3bbc95141423cda1bd1efcf6ca9db9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Apr 2021 07:05:44 +0000 Subject: [PATCH 081/230] Bump daggerVersion from 2.34.1 to 2.35 Bumps `daggerVersion` from 2.34.1 to 2.35. Updates `dagger` from 2.34.1 to 2.35 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.34.1...dagger-2.35) Updates `dagger-compiler` from 2.34.1 to 2.35 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.34.1...dagger-2.35) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 6b261d855d..2807a2f824 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -115,7 +115,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.34.1' + def daggerVersion = '2.35' def work_version = '2.5.0' def retrofit_version = '2.9.0' diff --git a/vector/build.gradle b/vector/build.gradle index c9941d3cb0..37fb7c20ce 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -297,7 +297,7 @@ dependencies { def big_image_viewer_version = '1.8.0' def glide_version = '4.12.0' def moshi_version = '1.12.0' - def daggerVersion = '2.34.1' + def daggerVersion = '2.35' def autofill_version = "1.1.0" def work_version = '2.5.0' def arch_version = '2.1.0' From ba7015c8f2b3249c33cbb6434e6352f5b7994818 Mon Sep 17 00:00:00 2001 From: Philip Munksgaard Date: Thu, 22 Apr 2021 09:14:39 +0000 Subject: [PATCH 082/230] Translated using Weblate (Danish) Currently translated at 20.8% (493 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/da/ --- vector/src/main/res/values-da/strings.xml | 151 +++++++++------------- 1 file changed, 61 insertions(+), 90 deletions(-) diff --git a/vector/src/main/res/values-da/strings.xml b/vector/src/main/res/values-da/strings.xml index d74de284f4..c6c854a128 100644 --- a/vector/src/main/res/values-da/strings.xml +++ b/vector/src/main/res/values-da/strings.xml @@ -1,12 +1,11 @@ - + %1$s: %2$s %1$s sendte et billede. - %ss invitation %1$s inviterede %2$s %1$s inviterede dig - %1$s forbandt + %1$s kom ind i rummet %1$s forlod rummet %1$s afviste invitationen %1$s kickede %2$s @@ -30,59 +29,44 @@ alle. ukendt (%s). %1$s slog ende-til-ende kryptering til (%2$s) - - %1$s forespurgte en VoIP konference - VoIP konference startet - VoIP konference afsluttet - + %1$s anmodede om en VoIP konference + VoIP-konference startet + VoIP-konference afsluttet (avatar blev også ændret) %1$s fjernede navnet på rummet %1$s fjernede emnet for rummet %1$s opdaterede sin profil %2$s %1$s inviterede %2$s til rummet %1$s accepterede invitationen til %2$s - ** Kunne ikke dekryptere: %s ** Afsenderens enhed har ikke sendt os nøglerne til denne besked. - Kunne ikke hemmeligholde Kunne ikke sende besked - Kunne ikke uploade billede - Netværks fejl Matrix fejl - Det er i øjeblikket ikke muligt at genforbinde til et tomt rum. - Krypteret besked - mailadresse Telefonnummer - Invitation fra %s Invitation til rum %1$s og %2$s - %1$s og 1 anden %1$s og %2$d andre - Tomt rum Lyst Tema Mørkt Tema Sort Tema - Synkroniserer Lyt efter begivenheder - Beskeder Rum Indstillinger Medlemsdetaljer Historisk - OK Afbryd Gem @@ -114,7 +98,6 @@ eller Invitér Offline - Log ud Stemmeopkald Videoopkald @@ -126,23 +109,18 @@ Luk Kopieret til udklipsholder Slå fra - Bekræftigelse Advarsel - Hjem Favoritter Folk Rum - Søg efter rum Søg efter favoritter Søg efter folk Søg efter rum - Invitationer Lav prioritet - Samtaler Lokal adressebog Bruger katalog @@ -150,7 +128,6 @@ Ingen samtaler Du gav ikke ${app_name} tilladelse til at se dine lokale kontakter Ingen resultater - Rum Rum katalog Ingen rum @@ -159,7 +136,6 @@ 1 bruger %d brugere - Send logfiler Send crashlogfiler Send screenshot @@ -169,14 +145,11 @@ For at diagnosticere problemer vil logs fra denne klient blive sendt sammen med fejlrapporten. Hvis du vil foretrække kun at sende teksten foroven, bedes du fjerne fluebenet: Du lader til at ryste din telefon i frustration. Vil du sende en fejlmeddelelse? Programmet crashede sidst. Vil du sende en crash rapport? - Fejlrapporten lykkedes med at blive sendt Fejlmeddelelsen kunne ikke sendes (%s) Fremskridt (%s%%) - Send ind i Læs - Forbind til Rum Brugernavn Registrér @@ -185,14 +158,11 @@ Home Server URL Identity Server URL Søg - Start Ny Chat Start Stemmeopkald Start Videoopkald - Send filer Tag billede eller video - Log ind Registrer Send @@ -240,7 +210,6 @@ Du kan tilføje din emailadresse til din profil i indstillinger. Dit password er blevet gendannet. Du er blevet logget ud af alle enheder og vil ikke længere modtage pushnotifikationer. For at slå notifikationer til igen skal du logge ind på hver enhed igen. - URLen skal starte med http[s]:// Kunne ikke logge ind: Netværksfejl Kunne ikke logge ind @@ -248,7 +217,6 @@ Du er blevet logget ud af alle enheder og vil ikke længere modtage pushnotifika Kunne ikke registrere Kunne ikke registrere : email ejerskabsfejl Skriv gyldig URL - Ugyldigt brugernavn/password Adgangstokenen du skrev blev ikke godkendt Fejlformet JSON @@ -256,30 +224,23 @@ Du er blevet logget ud af alle enheder og vil ikke længere modtage pushnotifika For mange forespørgsler blev sendt Brugernavnet er allerede i brug Emaillinket som ikke er blevet trykket på endnu - Larmende notifikationer Stille notifikationer - Tag billede Optag video - "Send som " Original Stor Middel Lille - Afbryd hentningen? Afbryd uploadningen? %d s %1$dm:%2$ds - I går I dag - Rum navn Rum emne - Opkald Opkald forbundet Opkald forbinder… @@ -289,15 +250,12 @@ Du er blevet logget ud af alle enheder og vil ikke længere modtage pushnotifika Indgående Videoopkald Indgående Stemmeopkald Opkald I Gang - Den anden side tog den ikke. Medie Forbindelse Mislykkedes Kan ikke starte kameraet opkald besvaret et andet sted - Tag et billede eller optag en video Kan ikke optage video - Information ${app_name} skal bruge tilladelse til at tilgå dit billed- og videoarkiv for at sende og gemme vedhæftninger. Giv venligst tilladelse ved næste pop-up for at kunne sende filer fra din telefon. @@ -318,47 +276,37 @@ Giv venligst tilladelse ved næste pop-up for at finde kontakter der er på ${ap ${app_name} skal bruge adgang til dine kontakter for at finde andre Matrix brugere ud fra deres email og telefonnumre. Vil du give ${app_name} adgang til dine kontakter? - Beklager… Handlingen blev ikke udført fordi der mangler tilladelser - Gemt Gem til downloads JA NEJ Fortsæt - Fjern Forbind Forhåndsvisning Afvis - Spring til første ulæste besked. - Du er blevet inviteret til at forbinde til dette rum af %s Denne invitation blev sendt til %s som ikke er tilknyttet denne konto. Det kan være du vil logge ind med en anden konto eller tilføje denne email til din konto. Du forsøger at tilgå %s. Vil du forbinde for at deltage i diskussionen? et rum Dette er en forhåndsvisning af dette rum. Interaktion med rummet er slået fra. - Ny chat Tilføj medlem 1 medlem - Forlad rum Er du sikker på at du vil forlade rummet? Er du sikker på du vil fjerne %s fra denne chat? Skab - Online Offline Passiv - ADMINSTRATIONSVÆRKTØJER OPKALD DIREKTE CHATS ENHEDER - Invitér Forlad dette rum Fjern fra dette rum @@ -374,18 +322,14 @@ Det kan være du vil logge ind med en anden konto eller tilføje denne email til Hvis Enhedsliste Du vil ikke kunne omgøre denne ændring da du forfremmer brugeren til at have samme magt niveau som dig selv. Er du sikker? - Er du sikker på du vil invitere %s til denne chat? - Invitér med ID LOKALE KONTAKTER (%d) BRUGERKATALOG (%s) Kun Matrix brugere - Invitér bruger med ID Indtast venligst en eller flere emails eller Matrix ID Email eller Matrix ID - Søg %s skriver… "%1$s & %2$s skriver…" @@ -401,7 +345,6 @@ Er du sikker? Slet usendte beskeder Fil ikke fundet Du har ikke tilladelse til at skrive i dette rum - Stol på Stol ikke på Log ud @@ -412,12 +355,9 @@ Er du sikker? Hvis serveradministratoren har sagt det er forventeligt skal du sikre dig at fingeraftrykket forneden passer med det fingeraftryk de har oplyst. Certifikatet har ændret sig fra det din telefon stolede på. Dette er HØJST USÆDVANLIGT. Det anbefales at du IKKE ACCEPTERE dette nye certifikat. Nug rapport - Kvitteringer for læsning - Certifikatet er ændret fra et tidligere betroet, til et der ikke er betroet. Serveren kan have fornyet sit certifikat. Kontakt server administratoren for det forventede fingeraftryk. Accepter kun certifikatet hvis server administratoren har publiceret et fingeraftryk der matcher det ovenstående. - Detaljer for rummet Personer Filer @@ -425,30 +365,22 @@ Er du sikker? Ugyldigt ID. Det skal være en e-mail adresse eller et Matrix ID som \'@lokaldel:domæne\' INVITERET Detaljer om community - Loader… - Afslut Handlinger Communities - Søg efter communities - Communities Ingen grupper - Er du sikker på, at du ønsker at starte en ny chat med %s? Er du sikker på, at du ønsker at starte et opkald? Er du sikker på, at du ønsker at starte et videoopkald\? - Inviter Liste over grupper - 1 medlemsændring %d medlemsændringer - Medlemsoversigt Synkroniserer… @@ -460,18 +392,14 @@ Er du sikker? %d medlemmer Er du sikker på, at du ønsker at blokere denne bruger fra denne chat? - 1 ny meddelelse %d nye meddelelser - Årsag til rapportering Ønsker du at skjule alle meddelelser fra denne bruger? Annuller upload Annuller download - - Søg Filtrer medlemmer i rum Ingen resultater @@ -479,7 +407,6 @@ Er du sikker? MEDDELELSER FOLK FILER - OVERSIGT FAVORITTER RUM @@ -488,7 +415,6 @@ Er du sikker? Start chat Opret rum Indtast ID eller alias på et rum - Gennemse oversigt 1 rum @@ -499,7 +425,6 @@ Er du sikker? %1$s rum fundet for %2$s Søger i liste… - Alle meddelelser (højlydt) Alle meddelelser Favorit @@ -508,7 +433,6 @@ Er du sikker? Forlad samtale Glem Opret genvej på startskærm - Meddelelser Indstillinger "Version " @@ -516,7 +440,6 @@ Er du sikker? Beskeder fra tredjepart Copyright Privacy-politik - Profilbillede Navn til visning Email @@ -524,7 +447,6 @@ Er du sikker? Telefon Tilføj telefonnummer Tænd skærmen 3 sekunder - Meddelelser med mit navn Meddelelser med mit brugernavn Meddelelser i direkte chats @@ -532,13 +454,11 @@ Er du sikker? Når jeg bliver inviteret til et rum Invitation til opkald Meddelelser sendt af bot - Start ved opstart Synkronisering i baggrunden Aktiver baggrundssynkronisering Timeout for synkroniseringskald Pause mellem to synkroniseringskald - Version Anvendelsesbetingelser Meddelelser fra tredjepart @@ -547,7 +467,6 @@ Er du sikker? Ryd cache Ryd mediecache Beholde mediefiler - Brugerindstillinger Ignorerede brugere Andet @@ -559,7 +478,6 @@ Er du sikker? Nøgle-backup Brug Nøgle-backup Bekræft session - Nøgle-backup er ikke færdig, vent venligst… Du vil miste dine krypterede beskeder, hvis du logger ud nu Jeg ønsker ikke mine krypterede beskeder @@ -567,7 +485,60 @@ Er du sikker? Er du sikker\? Back up Du vil miste adgang til dine krypterede beskeder, medmindre du tager backup af dine nøgler, før du logger ud. - Tredjeparts licenser - - + Du trak invitationen til %1$s tilbage + %1$s trak invitationen til %2$s tilbage + Du trak invitationen til %1$s tilbage + %1$s trak invitationen til %2$s tilbage + Du inviterede %1$s + %1$s inviterede %2$s + Du inviterede %1$s til rummet + Du fjernede navnet på rummet + Du anmodede om en VoIP-konference + 🎉 Alle servere er bannet fra at deltage! Dette rum kan ikke længere bruges. + Ingen ændring. + Du satte serverens ACLer for dette rum. + %s satte serverens ACLer for dette rum. + Du opgraderede her. + %s opgraderede her. + Du opgraderede dette rum. + %s opgraderede dette rum. + Du slog ende-til-ende kryptering til (%1$s) + Du gjorde fremtidige beskeder synlige for %1$s + %1$s gjorde fremtidige beskeder synlige for %2$s + Du gjorde den fremtidige historik for rummet synlig for %1$s + Du afsluttede opkaldet. + Du besvarede opkaldet. + Du sendte data til at forberede opkaldet. + %s sendte data til at forberede opkaldet. + Du foretog et opkald. + Du foretog et video-opkald. + Du skiftede rummets navn til: %1$s + Du skiftede rummets logo + %1$s skiftede rummets logo + Du skiftede emnet til: %1$s + Du fjernede dit viste navn (det var %1$s) + Du skiftede dit viste navn fra %1$s til %2$s + Du satte dit viste navn til %1$s + Du skiftede din avatar + Du trak %1$ss invitation tilbage + Du bannede %1$s + Du unbannede %1$s + Du smed %1$s ud + Du afslog invitationen + Du gik ud af rummet + %1$s gik ud af rummet + Du gik ud af rummet + Du kom ind + %1$s kom ind + Du kom ind i rummet + Du inviterede %1$s + Du oprettede diskussionen + %1$s oprettede diskussionen + Du oprettede rummet + %1$s oprettede rummet + Din invitation + Du har sendt et klistermærke. + %1$s har sendt et klistermærke. + Du har sendt et billede. + \ No newline at end of file From a35b63dbeb2c9e6356c75843652fe99784a98b73 Mon Sep 17 00:00:00 2001 From: Samu Voutilainen Date: Wed, 21 Apr 2021 13:56:31 +0000 Subject: [PATCH 083/230] Translated using Weblate (Finnish) Currently translated at 79.2% (1873 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- vector/src/main/res/values-fi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 33f19760af..e8786bdaf6 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -763,7 +763,7 @@ Huoneen osapuolten välisen salauksen avaimet tallennettiin tiedostoon \'%s\' \n \nVaroitus: tämä tiedosto saatetaan poistaa, mikäli Element poistetaan. - Tuo päästä päähän -salatun huoneen avaimet + Tuo salatun huoneen avaimet Tuo huoneen avaimet Tuo avaimet paikallisesta tiedostosta Tuo From 6d4b7eba5b46fdd589d10c07396305339c528617 Mon Sep 17 00:00:00 2001 From: Andrejs Date: Tue, 20 Apr 2021 11:08:28 +0000 Subject: [PATCH 084/230] Translated using Weblate (Latvian) Currently translated at 81.5% (1928 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/ --- vector/src/main/res/values-lv/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index 032c3d4d39..00274660c0 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -245,7 +245,7 @@ Iestatījumi Dalībnieka informācija Bijušie - Lai notiek + Labi Atcelt Saglabāt Pamest @@ -267,9 +267,9 @@ \nPievienojies ar %1$s vai %2$s iespēju Balss Video - Nevarēja sākt zvanu. Lūdzu, pamēģini vēlāk - Dēļ nepietiekamām atļaujām, dažas iespējas var nebūt pieejamas… - Tev ir nepieciešams iegūt atļauju, lai varētu sākt konferenci šajā istabā + Nevarēja sākt zvanu. Lūdzu, mēģiniet vēlāk + Dēļ nepietiekamām atļaujām dažas iespējas var nebūt pieejamas… + Jums nepieciešams iegūt atļauju, lai spētu uzaicināt sākt konferenci šajā istabā Nav iespējams sākt zvanu Ierīces informācija Konferences zvani netiek atbalstīti šifrētās istabās @@ -1228,7 +1228,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Gatavs Izlaist Pieņemt - Šajā istabā nav atļaujas sākt konferences zvanu + Jums nav atļaujas sākt konferences zvanu šajā istabā Atiestatīt Aizvērt Pauzēt @@ -1922,7 +1922,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Sistēmas Brīdinājumi Nepublicēt Nolikt - Zvanīt Tāpat + Zvanīt vienalga Neizdevās noņemt logrīku Neizdevās ielādēt logrīku Startē pakalpojumu @@ -2137,8 +2137,8 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Jums nav atļaujas sākt zvanu Jums nav atļaujas sākt zvanu šajā istabā Jums nav atļaujas sākt konferences zvanu - Trūkstošo atļauju dēļ, šī darbība nav iespējama. - Sākt Sarunu + Trūkstošo atļauju dēļ šī darbība nav iespējama. + Sākt sarunu Ja jūs nedublēsiet atslēgas pirms izrakstīšanās, zaudēsiet piekļuvi jūsu šifrētām ziņām. Drošs Atslēgu Dublējums jābūt ieslēgts visās jūsu sesijās, lai izvairītos no piekļuves zaudēšanas jūsu šifrētām ziņām. Dublēt From 52d8088d751e1474e76b9ba32bb8475b647de1fa Mon Sep 17 00:00:00 2001 From: Martin Hansen Date: Tue, 20 Apr 2021 19:49:35 +0000 Subject: [PATCH 085/230] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 66.4% (1571 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nb_NO/ --- vector/src/main/res/values-nb-rNO/strings.xml | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 1b4628c913..827c7ceaae 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -1615,4 +1615,89 @@ Play Tjenester Sjekk Søker i katalogen… INVITASJONER + Ikke noe innhold + Sender + Mislyktes + Sendt + Vil du avbryte sending av melding\? + Slett alle mislykkede meldinger + Slett usendte meldinger + Meldingene kunne ikke sendes + Er du sikker på at du vil slette alle usendte meldinger i dette rommet\? + %1$s og %2$s + Telefonnummer + Matrix feil + Nettverksfeil + Kunne ikke laste opp bildet + Kan ikke sende melding + Avsenderens enhet har ikke sendt oss nøklene til denne meldingen. + ** Kan ikke dekryptere: %s ** + Tilpasset + Tilpasset (%1$d) + Standard + Administrator + Du endret videokonferansen + Videokonferanse endret av %1$s + Du avsluttet videokonferansen + Videokonferanse avsluttet av %1$s + Videokonferanse startet av %1$s + Du startet videokonferanse + Du godtok invitasjonen til %1$s + Du inviterte %1$s + Du oppdaterte profilen din %1$s + %1$s oppdaterte profilen sin %2$s + Melding fjernet [årsak: %1$s] + Melding fjernet av %1$s + Meldingen er fjernet + (avatar ble også endret) + VoIP-konferansen avsluttet + VoIP-konferansen startet + Du ba om en VoIP-konferanse + %1$s ba om en VoIP-konferanse + Du oppgraderte dette rommet. + %s oppgradert dette rommet. + Du har slått på end-to-end-kryptering (%1$s) + %1$s slo på ende-til-ende-kryptering (%2$s) + Du gjorde fremtidig romhistorie synlig for %1$s + %1$s synliggjort fremtidig romhistorie for %2$s + Du avsluttet anropet. + %s avsluttet anropet. + %s sendte data for å konfigurere anropet. + Du sendte data for å konfigurere anropet. + %s svarte på anropet. + Du svarte på anropet. + Du plasserte en videosamtale. + %s plasserte et videosamtale. + %1$s endret romnavnet til: %2$s + Du byttet romavatar + %1$s endret romavataren + Du endret emnet til: %1$s + %1$s endret emnet til: %2$s + Du fjernet visningsnavnet ditt (det var %1$s) + Du byttet avatar + %1$s endret avatar + Du utestengte %1$s + Du sparket %1$s + Du avviste invitasjonen + %1$s avviste invitasjonen + Du forlot rommet + %1$s forlot rommet + Du forlot rommet + %1$s forlot rommet + Du ble med + %1$s ble med + Du ble med på rommet + %1$s ble med på rommet + %1$s inviterte deg + Du inviterte %1$s + %1$s inviterte %2$s + Du opprettet diskusjonen + %1$s opprettet diskusjonen + Du opprettet rommet + %1$s skapte rommet + Du sendte et klistremerke. + %1$s sendte et klistremerke. + Du sendte et bilde. + %1$s sendte et bilde. + %1$s: %2$s \ No newline at end of file From 149e9f6674a70d3d40c0dc8e116fa67be93b8160 Mon Sep 17 00:00:00 2001 From: Quang Trung Date: Wed, 21 Apr 2021 01:01:43 +0000 Subject: [PATCH 086/230] Translated using Weblate (Vietnamese) Currently translated at 18.7% (442 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/ --- vector/src/main/res/values-vi/strings.xml | 188 +++++++++++++++++++++- 1 file changed, 185 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 407fd31e8c..9b4459b255 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -151,9 +151,9 @@ Theo dõi sự kiện Đang đồng bộ hóa… Đang khởi tạo dịch vụ - Nền đen - Nền tối - Nền sáng + Chủ đề đen + Chủ đề tối + Chủ đề sáng Vui lòng kiểm tra email của bạn để tiếp tục quá trình đăng ký Quên mật khẩu\? Mật khẩu không trùng khớp @@ -273,4 +273,186 @@ Gửi bản tường thuật sự cố Gửi bản tường thuật Gửi + Kết nối trong nền + Chọn tuỳ chọn khác + Cấp quyền + Quản lý cài đặt khám phá của bạn. + Khám phá + Vô hiệu hoá tài khoản của tôi + Vô hiệu hoá tài khoản + • Máy chủ khớp với %s đã bị xoá khỏi danh sách được cho phép. + • Máy chủ khớp với %s bây giờ sẽ được cho phép. + • Máy chủ khớp với %s đã được xoá khỏi danh sách cấm. + • Máy chủ khớp với %s bây giờ sẽ bị cấm. + ${app_name} yêu cầu bạn nhập thông tin của bạn để thực hiện hành động này. + Cần xác thực lại + Người dùng + Đã xảy ra lỗi khi đang chuyển cuộc gọi + Chuyển + Kết nối + Một số tin nhắn chưa được gửi + Phòng này có bản nháp chưa được gửi + Xoá ảnh đại diện + Thay đổi ảnh đại diện + Hình ảnh + Xác thực thất bại + Video + Tin nhắn không được gửi vì có lỗi + Đóng trình chọn Emoji + Mở trình chọn Emoji + Cấp độ tin tưởng đã tin tưởng + Cấp độ tin tưởng cảnh báo + Cấp độ tin tưởng mặc định + Đã chọn + Gửi sự kiện tuỳ chỉnh + Khám phá trạng thái phòng + Công cụ phát triển + Xem xác nhận đã đọc + Không thông báo + Thông báo không có âm thanh + Thông báo có âm thanh + Không có nội dung + Bạn có muốn huỷ việc gửi tin nhắn không\? + Xoá tất cả tin nhắn bị thất bại + Thất bại + Đã gửi + Đang gửi + Số điện thoại + Địa chỉ email + Hiện tại chưa thể tham gia lại phòng trống. + Lỗi Matrix + %1$s đã thay đổi cấp độ quyền lực của %2$s. + Bạn đã thay đổi cấp độ quyền lực của %1$s. + Lỗi mạng + Tải hình ảnh lên thất bại + Không thể gửi tin nhắn + Không thể rút lại + Thiết bị của người gửi chưa gửi cho chúng tôi mã khoá cho tin nhắn này. + ** Không thể giải mã: %s ** + %1$s từ %2$s thành %3$s + Tuỳ chỉnh + Tuỳ chỉnh (%1$d) + Mặc định + Người điều hành + Quản trị viên + Bạn đã sửa đổi buổi hội thảo video + Buổi hội thảo video đã được %1$s sửa đổi + Bạn đã kết thúc buổi hội thảo video + Buổi hội thảo video đã được %1$s kết thúc + Bạn đã bắt đầu buổi hội thảo video + Buổi hội thảo video đã được %1$s bắt đầu + Bạn đã sửa đổi %1$s tiện ích + %1$s đã sửa đổi %2$s tiện ích + Bạn đã xoá %1$s tiện ích + %1$s đã xoá %2$s tiện ích + Bạn đã thêm %1$s tiện ích + %1$s đã thêm %2$s tiện ích + Bạn đã chấp nhận lời mời đến %1$s + %1$s đã chấp nhận lời mời đến %2$s + Bạn đã thu hồi lời mời đến %1$s + %1$s đã thu hồi lời mời đến %2$s + Bạn đã thu hồi lời mời đến %1$s để tham gia phòng + %1$s đã thu hồi lời mời đến %2$s để tham gia phòng + Bạn đã mời %1$s + %1$s đã mời %2$s + Bạn đã gửi lời mời đến %1$s để tham gia phòng + %1$s đã gửi lời mời đến %2$s để tham gia phòng + Tin nhắn đã bị %1$s xoá [lý do: %2$s] + Tin nhắn đã bị xoá [lý do: %1$s] + Tin nhắn đã bị %1$s xoá + Tin nhắn đã bị xoá + Bạn đã xoá ảnh đại diện của phòng + %1$s đã xoá ảnh đại diện của phòng + Bạn đã xoá chủ đề của phòng + %1$s đã xoá chủ đề của phòng + Bạn đã xoá tên phòng + %1$s đã xoá tên phòng + (ảnh đại diện cũng được thay đổi) + Buổi hội thảo VoIP đã hoàn thành + Buổi hội thảo VoIP đã bắt đầu + Bạn đã yêu cầu một buổi hội thảo VoIP + %1$s đã yêu cầu một buổi hội thảo VoIP + 🎉 Tất cả máy chủ bị cấm không được tham gia! Phòng này không thể được sử dụng nữa. + Không có thay đổi. + Bạn đã thay đổi ACL máy chủ cho phòng này. + %s đã thay đổi ACL máy chủ cho phòng này. + • Máy chủ khớp với %s được cho phép. + • Máy chủ khớp với %s bị cấm. + Bạn đã đặt ACL máy chủ cho phòng này. + %s đã đặt ACL máy chủ cho phòng này. + Bạn đã nâng cấp ở đây. + %s đã nâng cấp ở đây. + Bạn đã nâng cấp phòng này. + %s đã nâng cấp phòng này. + Bạn đã bật mã hoá đầu cuối (%1$s) + %1$s đã bật mã hoá đầu cuối (%2$s) + không xác định (%s). + bất kỳ ai. + tất cả thành viên phòng. + tất cả thành viên phòng, từ lúc họ tham gia. + tất cả thành viên phòng, từ lúc họ được mời. + Bạn đã làm các tin nhắn trong tương lai có thể được %1$s nhìn thấy + %1$s đã làm các tin nhắn trong tương lai có thể được %2$s nhìn thấy + Bạn đã làm lịch sử phòng trong tương lai có thể được %1$s nhìn thấy + %1$s đã làm lịch sử phòng trong tương lai có thể được %2$s nhìn thấy + Bạn đã cấm %1$s + %1$s đã cấm %2$s + Bạn đã bỏ cấm %1$s + %1$s đã bỏ cấm %2$s + Bạn đã xoá tên hiển thị (nó đã là %1$s) + %1$s đã xoá tên hiển thị (nó đã là %2$s) + Bạn đã đổi tên hiển thị từ %1$s thành %2$s + %1$s đã đổi tên hiển thị từ %2$s thành %3$s + Bạn đã đặt tên hiển thị thành %1$s + %1$s đã đặt tên hiển thị thành %2$s + Bạn đã thay đổi ảnh đại diện + %1$s đã thay đổi ảnh đại diện + Bạn đã rút lại lời mời của %1$s + %1$s đã rút lại lời mời của %2$s + Chủ đề của bạn + Chủ đề + Bạn đã kết thúc cuộc gọi. + %s đã kết thúc cuộc gọi. + Bạn đã trả lời cuộc gọi. + %s đã trả lời cuộc gọi. + Bạn đã gửi dữ liệu để thiết lập cuộc gọi. + %s đã gửi dữ liệu để thiết lập cuộc gọi. + Bạn đã đặt một cuộc gọi thoại. + %s đã đặt một cuộc gọi thoại. + Bạn đã đặt một cuộc gọi video. + %s đã đặt một cuộc gọi video. + Bạn đã đổi tên phòng thành: %1$s + %1$s đã đổi tên phòng thành: %2$s + Bạn đã thay đổi ảnh đại diện của phòng + %1$s đã thay đổi ảnh đại diện của phòng + %1$s đã thay đổi chủ đề thành: %2$s + Bạn đã thay đổi chủ đề thành: %1$s + Xoá các tin nhắn chưa được gửi + Bạn có chắc bạn muốn xoá tất cả tin nhắn chưa được gửi trong phòng này không\? + Bạn đã kick %1$s + %1$s đã kick %2$s + Bạn đã từ chối lời mời + %1$s đã từ chối lời mời + Bạn đã rời phòng + %1$s đã rời phòng + Bạn đã rời phòng + %1$s đã rời phòng + Bạn đã tham gia + %1$s đã tham gia + Bạn đã tham gia phòng + %1$s đã tham gia phòng + %1$s đã mời bạn + Bạn đã mời %1$s + %1$s đã mời %2$s + Bạn đã tạo cuộc thảo luận + %1$s đã tạo cuộc thảo luận + Bạn đã tạo phòng + %1$s đã tạo phòng + Lời mời của bạn + Lời mời của %s + Bạn đã gửi một sticker. + %1$s đã gửi một sticker. + Bạn đã gửi một hình ảnh. + %1$s đã gửi một hình ảnh. + %1$s: %2$s \ No newline at end of file From 63f1efd32081d254953f3c163a12483615c8e0c7 Mon Sep 17 00:00:00 2001 From: Deleted User Date: Fri, 23 Apr 2021 19:28:46 +0000 Subject: [PATCH 087/230] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (15 of 15 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/nb_NO/ --- .../android/nb-NO/changelogs/40100120.txt | 2 ++ .../android/nb-NO/changelogs/40100130.txt | 2 ++ .../android/nb-NO/changelogs/40100140.txt | 2 ++ .../android/nb-NO/changelogs/40100150.txt | 2 ++ .../android/nb-NO/changelogs/40100160.txt | 2 ++ .../android/nb-NO/changelogs/40100170.txt | 2 ++ .../android/nb-NO/changelogs/40101000.txt | 2 ++ .../android/nb-NO/changelogs/40101010.txt | 2 ++ .../android/nb-NO/changelogs/40101020.txt | 2 ++ .../android/nb-NO/changelogs/40101030.txt | 2 ++ .../android/nb-NO/full_description.txt | 30 +++++++++++++++++++ 11 files changed, 50 insertions(+) create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40100120.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40100130.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40100140.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40100150.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40100160.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40101010.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40101020.txt create mode 100644 fastlane/metadata/android/nb-NO/changelogs/40101030.txt create mode 100644 fastlane/metadata/android/nb-NO/full_description.txt diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100120.txt b/fastlane/metadata/android/nb-NO/changelogs/40100120.txt new file mode 100644 index 0000000000..163cd64cdc --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40100120.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: URL-forhåndsvisning, nytt Emoji-tastatur, nye rominnstillingsmuligheter og snø til jul! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.12 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100130.txt b/fastlane/metadata/android/nb-NO/changelogs/40100130.txt new file mode 100644 index 0000000000..23ab42ef2c --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40100130.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: URL-forhåndsvisning, nytt Emoji-tastatur, nye rominnstillingsmuligheter og snø til jul! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100140.txt b/fastlane/metadata/android/nb-NO/changelogs/40100140.txt new file mode 100644 index 0000000000..10a3d9b925 --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40100140.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: Rediger romtillatelser, automatisk lys/mørkt tema og en haug med feilrettinger. +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100150.txt b/fastlane/metadata/android/nb-NO/changelogs/40100150.txt new file mode 100644 index 0000000000..3237da115d --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40100150.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: Sosial innloggingsstøtte. +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100160.txt b/fastlane/metadata/android/nb-NO/changelogs/40100160.txt new file mode 100644 index 0000000000..5502fd3ab1 --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40100160.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: Sosial innloggingsstøtte. +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15 og https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40100170.txt b/fastlane/metadata/android/nb-NO/changelogs/40100170.txt new file mode 100644 index 0000000000..f9174a2ee4 --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40100170.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: Feilrettinger! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101000.txt b/fastlane/metadata/android/nb-NO/changelogs/40101000.txt new file mode 100644 index 0000000000..370dbb36ce --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: forbedring av VoIP (lyd og videosamtaler i DM) og feilrettinger! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101010.txt b/fastlane/metadata/android/nb-NO/changelogs/40101010.txt new file mode 100644 index 0000000000..c6109b8d9b --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101020.txt b/fastlane/metadata/android/nb-NO/changelogs/40101020.txt new file mode 100644 index 0000000000..9464c6fb0f --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/nb-NO/changelogs/40101030.txt b/fastlane/metadata/android/nb-NO/changelogs/40101030.txt new file mode 100644 index 0000000000..1e12246e9a --- /dev/null +++ b/fastlane/metadata/android/nb-NO/changelogs/40101030.txt @@ -0,0 +1,2 @@ +Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger! +Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.3 diff --git a/fastlane/metadata/android/nb-NO/full_description.txt b/fastlane/metadata/android/nb-NO/full_description.txt new file mode 100644 index 0000000000..92a3c4c5c3 --- /dev/null +++ b/fastlane/metadata/android/nb-NO/full_description.txt @@ -0,0 +1,30 @@ +Element er en ny type messenger og samarbeidsapp som: + +1. Gir deg kontrollen for å bevare personvernet ditt +2. Lar deg kommunisere med hvem som helst i Matrix-nettverket, og til og med ved å integrere med apper som Slack +3. Beskytter deg mot reklame, datamining og inngjerdede hager +4. Sikrer deg gjennom end-to-end-kryptering, med kryssignering for å bekrefte andre + +Element er helt forskjellig fra andre meldings- og samarbeidsapper fordi det er desentralisert og åpen kildekode. + +Element lar deg selv være vert - eller velge en vert - slik at du har personvern, eierskap og kontroll over dataene og samtalene dine. Det gir deg tilgang til et åpent nettverk; slik at du ikke bare holder på å snakke med bare andre Element-brukere. Og det er veldig sikkert. + +Element er i stand til å gjøre alt dette fordi det opererer på Matrix - standarden for åpen, desentralisert kommunikasjon. + +Element setter deg i kontroll ved å la deg velge hvem som er vert for samtalene dine. Fra Element-appen kan du velge å være vert på forskjellige måter: + +1. Få en gratis konto på matrix.org-serveren som er vert for Matrix-utviklerne, eller velg blant tusenvis av offentlige servere som er vert for frivillige +2. Vær vert for kontoen din ved å kjøre en server på din egen maskinvare +3. Registrer deg for en konto på en tilpasset server ved å bare abonnere på Hosting Matrix Services-vertsplattformen + + Hvorfor velge Element? + + EGNE DATA DINE : Du bestemmer hvor du vil oppbevare dataene og meldingene dine. Du eier den og kontrollerer den, ikke noe MEGACORP som utvinner dataene dine eller gir tilgang til tredjeparter. + + ÅPEN MELDING OG SAMARBEID : Du kan chatte med alle andre i Matrix-nettverket, enten de bruker Element eller en annen Matrix-app, og selv om de bruker et annet meldingssystem som Slack, IRC eller XMPP. + + SUPER-SECURE : Ekte end-to-end-kryptering (bare de i samtalen kan dekryptere meldinger), og kryssignering for å verifisere enhetene til samtaledeltakerne. + + KOMPLETT KOMMUNIKASJON : Meldinger, tale- og videosamtaler, fildeling, skjermdeling og en hel haug med integrasjoner, bots og widgets. Bygg rom, lokalsamfunn, hold kontakten og få ting gjort. + + ALT DER DU ER : Hold kontakten uansett hvor du er med fullt synkronisert meldingslogg på alle enhetene dine og på nettet på https://app.element.io. From 4608888b833c53af3c3a678bdc608eaaa3920e39 Mon Sep 17 00:00:00 2001 From: Quang Trung Date: Sun, 25 Apr 2021 01:24:42 +0000 Subject: [PATCH 088/230] Translated using Weblate (Vietnamese) Currently translated at 19.0% (449 of 2363 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/ --- vector/src/main/res/values-vi/strings.xml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index 9b4459b255..a1566a81d2 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -1,7 +1,7 @@ Lắc điện thoại để báo cáo lỗi - Có vẻ bạn đang lắc điện thoại một cách tức giận. Bạn có muốn báo cáo lỗi không\? + Có vẻ bạn đang lắc điện thoại một cách tức giận. Bạn có muốn mở màn hình báo cáo lỗi không\? Miêu tả vấn đề của bạn ở đây Nếu có thể, vui lòng viết miêu tả bằng tiếng Anh. Vui lòng miêu tả lỗi. Bạn đã làm gì\? Bạn mong điều gì xảy ra\? Điều gì đã xảy ra\? @@ -27,7 +27,7 @@ Chỉ những liên hệ Matrix Thư mục người dùng Sổ địa chỉ địa phương - Các cuộc trò chuyện + Cuộc trò chuyện Cảnh báo hệ thống Ưu tiên thấp Lời mời @@ -53,7 +53,7 @@ Đánh dấu đã đọc Trả lời nhanh Đánh dấu tất cả đã đọc - Tìm toàn bộ + Tìm kiếm trong toàn bộ Cuộc gọi bằng video Cuộc gọi bằng giọng nói Bạn có chắc mình muốn đăng xuất không\? @@ -209,9 +209,9 @@ Cuộc gọi ${app_name} thất bại Đừng hỏi lại tôi Hãy thử dùng %s - Vui lòng yêu cầu quản trị viên của máy chủ nhà của bạn (%1$s) thiết lập một máy chủ TURN để các cuộc gọi có thể hoạt động […]. + Vui lòng yêu cầu quản trị viên của máy chủ nhà của bạn (%1$s) thiết lập một máy chủ TURN để các cuộc gọi có thể hoạt động một cách đáng tin cậy. \n -\nBạn cũng có thể thử dùng máy chủ công cộng ở %2$s, nhưng cách này sẽ không […] bằng, và địa chỉ IP của bạn cũng sẽ được chia sẻ đến server đó. Bạn có thể quản lý việc này trong mục Cài đặt. +\nBạn cũng có thể thử dùng máy chủ công cộng ở %2$s, nhưng cách này sẽ không đáng tin cậy bằng, và địa chỉ IP của bạn sẽ được chia sẻ đến máy chủ đó. Bạn cũng có thể quản lý việc này trong Cài đặt. Cuộc gọi thất bại do thiết lập máy chủ sai Bạn có chắc bạn muốn bắt đầu một cuộc gọi video\? Bạn có chắc bạn muốn bắt đầu một cuộc gọi bằng giọng nói\? @@ -455,4 +455,6 @@ Bạn đã gửi một hình ảnh. %1$s đã gửi một hình ảnh. %1$s: %2$s + Bạn đã cập nhật hồ sơ của bạn %1$s + %1$s đã cập nhật hồ sơ của họ %2$s \ No newline at end of file From ae4ae4111dc22bcabb0e7d036291c898e1b3c4eb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 26 Apr 2021 10:55:25 +0200 Subject: [PATCH 089/230] Restore rx methods, may be used by forks... --- .../src/main/java/org/matrix/android/sdk/rx/RxRoom.kt | 11 +++++++++++ .../main/java/org/matrix/android/sdk/rx/RxSession.kt | 11 +++++++++++ 2 files changed, 22 insertions(+) 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 3f61ca0103..21db4e1893 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 @@ -19,7 +19,9 @@ package org.matrix.android.sdk.rx import android.net.Uri import io.reactivex.Completable import io.reactivex.Observable +import io.reactivex.Single import kotlinx.coroutines.rx2.rxCompletable +import kotlinx.coroutines.rx2.rxSingle 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.identity.ThreePid @@ -89,6 +91,15 @@ class RxRoom(private val room: Room) { return room.getMyReadReceiptLive().asObservable() } + fun loadRoomMembersIfNeeded(): Single = rxSingle { + room.loadRoomMembersIfNeeded() + } + + fun joinRoom(reason: String? = null, + viaServers: List = emptyList()): Single = rxSingle { + room.join(reason, viaServers) + } + fun liveEventReadReceipts(eventId: String): Observable> { return room.getEventReadReceiptsLive(eventId).asObservable() } diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index 5d8b019284..0fe2b01576 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -123,12 +124,22 @@ class RxSession(private val session: Session) { .startWithCallable { session.getPendingThreePids() } } + fun createRoom(roomParams: CreateRoomParams): Single = rxSingle { + session.createRoom(roomParams) + } + fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set): Single> = rxSingle { session.searchUsersDirectory(search, limit, excludedUserIds) } + fun joinRoom(roomIdOrAlias: String, + reason: String? = null, + viaServers: List = emptyList()): Single = rxSingle { + session.joinRoom(roomIdOrAlias, reason, viaServers) + } + fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Single> = rxSingle { session.getRoomIdByAlias(roomAlias, searchOnServer) From bcbc6c0f7c1fc9c190abd64c954bf0c4132e86ba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 26 Apr 2021 11:00:57 +0200 Subject: [PATCH 090/230] Add missing loading state --- .../app/features/createdirect/CreateDirectRoomViewModel.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index 0cc128f749..9d6f6da54b 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -19,6 +19,7 @@ package im.vector.app.features.createdirect import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext @@ -82,6 +83,8 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } private fun createRoomAndInviteSelectedUsers(selections: Set) { + setState { copy(createAndInviteState = Loading()) } + viewModelScope.launch(Dispatchers.IO) { val adminE2EByDefault = rawService.getElementWellknown(session.myUserId) ?.isE2EByDefault() From 08cdac3e0df7756c95290015df881532fe40421d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 26 Apr 2021 11:05:18 +0200 Subject: [PATCH 091/230] Handle PR review remarks --- .../im/vector/app/core/mvrx/ResultExtension.kt | 15 +++++++++++---- .../createdirect/CreateDirectRoomViewModel.kt | 6 +++--- .../home/room/detail/RoomDetailViewModel.kt | 6 +++--- .../RoomMemberProfileViewModel.kt | 6 +++--- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt b/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt index 1dbf658deb..dfd04ea6f6 100644 --- a/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt +++ b/vector/src/main/java/im/vector/app/core/mvrx/ResultExtension.kt @@ -20,7 +20,14 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Success -fun Result.foldToAsync(): Async = fold( - { Success(it) }, - { Fail(it) } -) +/** + * Note: this will be removed when upgrading to mvrx2 + */ +suspend fun runCatchingToAsync(block: suspend () -> A): Async { + return runCatching { + block.invoke() + }.fold( + { Success(it) }, + { Fail(it) } + ) +} diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index 9d6f6da54b..2f0b6e5ec9 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -27,7 +27,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.mvrx.foldToAsync +import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault @@ -102,9 +102,9 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - val result = runCatching { + val result = runCatchingToAsync { session.createRoom(roomParams) - }.foldToAsync() + } setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 725cce70ab..006a2c9b5f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -35,7 +35,7 @@ import dagger.assisted.AssistedInject import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.mvrx.foldToAsync +import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.call.dialpad.DialPadLookup @@ -651,10 +651,10 @@ class RoomDetailViewModel @AssistedInject constructor( ?.let { listOf(it) } .orEmpty() viewModelScope.launch { - val result = runCatching { + val result = runCatchingToAsync { session.joinRoom(roomId, viaServers = viaServers) roomId - }.foldToAsync() + } setState { copy(tombstoneEventHandling = result) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 0fa681a799..7b05c6c7f5 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -29,7 +29,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import im.vector.app.R -import im.vector.app.core.mvrx.foldToAsync +import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.powerlevel.PowerLevelsObservableFactory @@ -267,7 +267,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v } private suspend fun fetchProfileInfo() { - val result = runCatching { + val result = runCatchingToAsync { session.getProfile(initialState.userId) .let { MatrixItem.UserItem( @@ -276,7 +276,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v avatarUrl = it[ProfileService.AVATAR_URL_KEY] as? String ) } - }.foldToAsync() + } setState { copy(userMatrixItem = result) From ed9db95aa063c38f48fefd96b76d8f917fc864a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 14:27:59 +0000 Subject: [PATCH 092/230] Bump fragment-ktx from 1.3.2 to 1.3.3 Bumps fragment-ktx from 1.3.2 to 1.3.3. Signed-off-by: dependabot[bot] --- multipicker/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/multipicker/build.gradle b/multipicker/build.gradle index 26afd5fb77..5eff2ec3ec 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -43,7 +43,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' - implementation "androidx.fragment:fragment-ktx:1.3.2" + implementation "androidx.fragment:fragment-ktx:1.3.3" implementation 'androidx.exifinterface:exifinterface:1.3.2' // Log diff --git a/vector/build.gradle b/vector/build.gradle index cb4f26b71f..69d2037255 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -291,7 +291,7 @@ android { dependencies { def epoxy_version = '4.5.0' - def fragment_version = '1.3.2' + def fragment_version = '1.3.3' def arrow_version = "0.8.2" def markwon_version = '4.1.2' def big_image_viewer_version = '1.8.0' From 49d1b3e583b846d86e3330445b0e8c5077b83c91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 14:31:37 +0000 Subject: [PATCH 093/230] Bump zxcvbn from 1.4.1 to 1.5.0 Bumps [zxcvbn](https://github.com/nulab/zxcvbn4j) from 1.4.1 to 1.5.0. - [Release notes](https://github.com/nulab/zxcvbn4j/releases) - [Changelog](https://github.com/nulab/zxcvbn4j/blob/master/CHANGELOG.md) - [Commits](https://github.com/nulab/zxcvbn4j/compare/1.4.1...1.5.0) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index d13742d28b..32565f01ca 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -388,7 +388,7 @@ dependencies { implementation 'androidx.browser:browser:1.3.0' // Passphrase strength helper - implementation 'com.nulab-inc:zxcvbn:1.4.1' + implementation 'com.nulab-inc:zxcvbn:1.5.0' //Alerter implementation 'com.tapadoo.android:alerter:7.0.1' From c5fa0a413f5ae3eec898e7c4fdb8bac0454868df Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 29 Dec 2020 17:34:25 +0100 Subject: [PATCH 094/230] Space first commit --- .../org/matrix/android/sdk/rx/RxSession.kt | 9 + .../matrix/android/sdk/api/session/Session.kt | 6 + .../sdk/api/session/events/model/EventType.kt | 1 + .../session/room/RoomSummaryQueryParams.kt | 4 + .../api/session/room/model/IRoomSummary.kt | 31 ++++ .../sdk/api/session/room/model/RoomSummary.kt | 27 +-- .../sdk/api/session/room/model/RoomType.kt | 23 +++ .../room/model/create/CreateRoomParams.kt | 15 +- .../room/model/create/RoomCreateContent.kt | 4 +- .../api/session/space/CreateSpaceParams.kt | 27 +++ .../android/sdk/api/session/space/Space.kt | 26 +++ .../sdk/api/session/space/SpaceService.kt | 45 +++++ .../sdk/api/session/space/SpaceSummary.kt | 26 +++ .../session/space/model/SpaceChildContent.kt | 52 ++++++ .../matrix/android/sdk/api/util/MatrixItem.kt | 9 +- .../database/RealmSessionStoreMigration.kt | 20 +- .../database/mapper/RoomSummaryMapper.kt | 3 +- .../database/mapper/SpaceSummaryMapper.kt | 34 ++++ .../database/model/SessionRealmModule.kt | 3 +- .../database/model/SpaceSummaryEntity.kt | 41 +++++ .../query/SpaceSummaryEntityQueries.kt | 55 ++++++ .../sdk/internal/session/DefaultSession.kt | 6 +- .../sdk/internal/session/room/RoomModule.kt | 5 + .../relationship/RoomRelationshipHelper.kt | 50 +++++ .../room/summary/RoomSummaryDataSource.kt | 4 + .../room/summary/RoomSummaryUpdater.kt | 18 ++ .../internal/session/space/DefaultSpace.kt | 38 ++++ .../session/space/DefaultSpaceService.kt | 70 +++++++ .../session/space/SpaceSummaryDataSource.kt | 93 ++++++++++ .../im/vector/app/core/di/FragmentModule.kt | 6 + .../im/vector/app/core/di/VectorComponent.kt | 3 + .../im/vector/app/features/command/Command.kt | 3 +- .../app/features/command/CommandParser.kt | 12 ++ .../app/features/command/ParsedCommand.kt | 1 + .../features/grouplist/GroupListViewModel.kt | 7 +- .../grouplist/HomeSpaceSummaryItem.kt | 62 +++++++ .../grouplist/SelectedSpaceDataSource.kt | 26 +++ .../features/grouplist/SpaceListFragment.kt | 84 +++++++++ .../grouplist/SpaceSummaryController.kt | 76 ++++++++ .../features/grouplist/SpaceSummaryItem.kt | 57 ++++++ .../app/features/home/AvatarRenderer.kt | 31 +++- .../app/features/home/HomeDetailFragment.kt | 28 +++ .../app/features/home/HomeDetailViewModel.kt | 13 ++ .../app/features/home/HomeDetailViewState.kt | 2 + .../app/features/home/HomeDrawerFragment.kt | 7 +- .../home/room/detail/RoomDetailViewModel.kt | 18 ++ .../createroom/CreateRoomController.kt | 8 +- .../createroom/CreateRoomViewModel.kt | 18 +- .../createroom/CreateRoomViewState.kt | 10 +- .../features/settings/VectorPreferences.kt | 6 + .../features/spaces/SpacesListViewModel.kt | 172 ++++++++++++++++++ .../src/main/res/drawable/bg_group_item.xml | 2 +- .../src/main/res/drawable/bg_space_item.xml | 27 +++ .../res/drawable/ic_selected_community.xml | 9 + .../src/main/res/drawable/ic_space_home.xml | 12 ++ .../res/drawable/space_home_background.xml | 13 ++ .../main/res/layout/fragment_home_detail.xml | 51 ++++-- vector/src/main/res/layout/item_space.xml | 63 +++++++ vector/src/main/res/values/strings.xml | 4 + .../src/main/res/xml/vector_settings_labs.xml | 6 + 60 files changed, 1523 insertions(+), 59 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt create mode 100644 vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt create mode 100644 vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt create mode 100644 vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt create mode 100644 vector/src/main/res/drawable/bg_space_item.xml create mode 100644 vector/src/main/res/drawable/ic_selected_community.xml create mode 100644 vector/src/main/res/drawable/ic_space_home.xml create mode 100644 vector/src/main/res/drawable/space_home_background.xml create mode 100644 vector/src/main/res/layout/item_space.xml diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index 0fe2b01576..7cc0d69bb9 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -39,6 +39,8 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -66,6 +68,13 @@ class RxSession(private val session: Session) { } } + fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable> { + return session.spaceService().getSpaceSummariesLive(queryParams).asObservable() + .startWithCallable { + session.spaceService().getSpaceSummaries(queryParams) + } + } + fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable> { return session.getBreadcrumbsLive(queryParams).asObservable() .startWithCallable { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index a15799d862..5f442c33f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.search.SearchService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService +import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.sync.FilterService import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.terms.TermsService @@ -227,6 +228,11 @@ interface Session : */ fun thirdPartyService(): ThirdPartyService + /** + * Returns the space service associated with the session + */ + fun spaceService(): SpaceService + /** * Add a listener to the session. * @param listener the listener to add. 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 905e18b8e8..9d8f18e912 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 @@ -51,6 +51,7 @@ 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" + const val STATE_SPACE_CHILD = "m.space.child" /** * Note that this Event has been deprecated, see diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index 7e04ebb5f2..c8d52302e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomType fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams { return RoomSummaryQueryParams.Builder().apply(init).build() @@ -36,6 +37,7 @@ data class RoomSummaryQueryParams( val memberships: List, val roomCategoryFilter: RoomCategoryFilter?, val roomTagQueryFilter: RoomTagQueryFilter? + val excludeType: List ) { class Builder { @@ -46,6 +48,7 @@ data class RoomSummaryQueryParams( var memberships: List = Membership.all() var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL var roomTagQueryFilter: RoomTagQueryFilter? = null + var excludeType: List = listOf(RoomType.SPACE) fun build() = RoomSummaryQueryParams( roomId = roomId, @@ -54,6 +57,7 @@ data class RoomSummaryQueryParams( memberships = memberships, roomCategoryFilter = roomCategoryFilter, roomTagQueryFilter = roomTagQueryFilter + excludeType = excludeType ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt new file mode 100644 index 0000000000..1724f00c99 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model + +interface IRoomSummary { + val roomId: String + val displayName: String + val name: String + val topic: String + val avatarUrl: String + val canonicalAlias: String? + val aliases: List + val joinedMembersCount: Int? + val invitedMembersCount: Int? + val otherMemberIds: List + val roomType: String? +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index 9455a83aff..ac87a16911 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -27,19 +27,19 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent * It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] */ data class RoomSummary constructor( - val roomId: String, + override val roomId: String, // Computed display name - val displayName: String = "", - val name: String = "", - val topic: String = "", - val avatarUrl: String = "", - val canonicalAlias: String? = null, - val aliases: List = emptyList(), - val isDirect: Boolean = false, - val joinedMembersCount: Int? = 0, - val invitedMembersCount: Int? = 0, + override val displayName: String = "", + override val name: String = "", + override val topic: String = "", + override val avatarUrl: String = "", + override val canonicalAlias: String? = null, + override val aliases: List = emptyList(), + override val joinedMembersCount: Int? = 0, + override val invitedMembersCount: Int? = 0, val latestPreviewableEvent: TimelineEvent? = null, - val otherMemberIds: List = emptyList(), + override val otherMemberIds: List = emptyList(), + val isDirect: Boolean = false, val notificationCount: Int = 0, val highlightCount: Int = 0, val hasUnreadMessages: Boolean = false, @@ -54,8 +54,9 @@ data class RoomSummary constructor( val inviterId: String? = null, val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null, - val hasFailedSending: Boolean = false -) { + val hasFailedSending: Boolean = false, + override val roomType: String? = null +) : IRoomSummary { val isVersioned: Boolean get() = versioningState != VersioningState.NONE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt new file mode 100644 index 0000000000..3958d45d0b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.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.model + +object RoomType { + + const val SPACE = "m.space" + const val MESSAGING = "m.messaging" +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index 80e3741a0c..6009649314 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -21,10 +21,11 @@ import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM // TODO Give a way to include other initial states -class CreateRoomParams { +open class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. * A private visibility will hide the room from the published room list. @@ -111,6 +112,17 @@ class CreateRoomParams { } } + var roomType: String? = RoomType.MESSAGING + set(value) { + field = value + if (value != null) { + creationContent[CREATION_CONTENT_KEY_ROOM_TYPE] = value + } else { + // This is the default value, we remove the field + creationContent.remove(CREATION_CONTENT_KEY_ROOM_TYPE) + } + } + /** * The power level content to override in the default power level event */ @@ -138,5 +150,6 @@ class CreateRoomParams { companion object { private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" + private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt index 0b595b1b2b..52e5c0e9c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt @@ -26,5 +26,7 @@ import com.squareup.moshi.JsonClass data class RoomCreateContent( @Json(name = "creator") val creator: String? = null, @Json(name = "room_version") val roomVersion: String? = null, - @Json(name = "predecessor") val predecessor: Predecessor? = null + @Json(name = "predecessor") val predecessor: Predecessor? = null, + // Defines the room type, see #RoomType (user extensible) + @Json(name = "type") val type: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt new file mode 100644 index 0000000000..0caa7af14c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt @@ -0,0 +1,27 @@ +/* + * 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.space + +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams + +class CreateSpaceParams : CreateRoomParams() { + + init { + roomType = RoomType.SPACE + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt new file mode 100644 index 0000000000..75480282fa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -0,0 +1,26 @@ +/* + * 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.space + +import org.matrix.android.sdk.api.session.room.Room + +interface Space { + + fun asRoom() : Room + + suspend fun addRoom(roomId: String) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt new file mode 100644 index 0000000000..0c3461f1ca --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -0,0 +1,45 @@ +/* + * 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.space + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams + +typealias SpaceSummaryQueryParams = RoomSummaryQueryParams + +interface SpaceService { + + /** + * Create a room asynchronously + */ + suspend fun createSpace(params: CreateSpaceParams): String + + /** + * Get a space from a roomId + * @param roomId the roomId to look for. + * @return a room with roomId or null if room type is not space + */ + fun getSpace(spaceId: String): Space? + + /** + * Get a live list of space summaries. This list is refreshed as soon as the data changes. + * @return the [LiveData] of List[SpaceSummary] + */ + fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> + + fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt new file mode 100644 index 0000000000..d2be2f18f1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt @@ -0,0 +1,26 @@ +/* + * 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.space + +import org.matrix.android.sdk.api.session.room.model.IRoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +data class SpaceSummary( + val spaceId: String, + val roomSummary: RoomSummary, + val children: List +) : IRoomSummary by roomSummary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt new file mode 100644 index 0000000000..f65318b543 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -0,0 +1,52 @@ +/* + * 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.space.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * "content": { + * "via": ["example.com"], + * "present": true, + * "order": "abcd", + * "default": true + * } + */ +@JsonClass(generateAdapter = true) +data class SpaceChildContent( + /** + * Key which gives a list of candidate servers that can be used to join the room + */ + @Json(name = "via") val via: List? = null, + /** + * present: true key is included to distinguish from a deleted state event + */ + @Json(name = "present") val present: Boolean? = false, + /** + * The order key is a string which is used to provide a default ordering of siblings in the room list. + * (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last. + * orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~), + * or consist of more than 50 characters, are forbidden and should be ignored if received.) + */ + @Json(name = "order") val order: String? = null, + /** + * The default flag on a child listing allows a space admin to list the "default" sub-spaces and rooms in that space. + * This means that when a user joins the parent space, they will automatically be joined to those default children. + */ + @Json(name = "default") val default: Boolean? = true +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index db229a6453..a792248764 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.sender.SenderInfo +import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.user.model.User import java.util.Locale @@ -86,9 +87,9 @@ sealed class MatrixItem( } protected fun checkId() { - if (!id.startsWith(getIdPrefix())) { - error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}") - } +// if (!id.startsWith(getIdPrefix())) { +// error("Wrong usage of MatrixItem: check the id $id should start with ${getIdPrefix()}") +// } } /** @@ -151,6 +152,8 @@ fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatar fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) +fun SpaceSummary.toMatrixItem() = MatrixItem.RoomItem(spaceId, displayName, avatarUrl) + // If no name is available, use room alias as Riot-Web does fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 1daae906f2..2c06a4e8f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -29,15 +29,17 @@ import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityField import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields + import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields import timber.log.Timber import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 9L + const val SESSION_STORE_SCHEMA_VERSION = 10L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -52,6 +54,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 6) migrateTo7(realm) if (oldVersion <= 7) migrateTo8(realm) if (oldVersion <= 8) migrateTo9(realm) + if (oldVersion <= 9) migrateTo10(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -194,4 +197,19 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { } } } + + fun migrateTo10(realm: DynamicRealm) { + Timber.d("Step 9 -> 10") + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java) + ?.transform { obj -> + // Should I put messaging type here? + obj.setString(RoomSummaryEntityFields.ROOM_TYPE, null) + } + + realm.schema.create("SpaceSummaryEntity") + ?.addField(SpaceSummaryEntityFields.SPACE_ID, String::class.java, FieldAttribute.PRIMARY_KEY) + ?.addRealmObjectField(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + ?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, realm.schema.get("RoomSummaryEntity")!!) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 6dc70b60fc..c74eb4460d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -63,7 +63,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex, roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel, inviterId = roomSummaryEntity.inviterId, - hasFailedSending = roomSummaryEntity.hasFailedSending + hasFailedSending = roomSummaryEntity.hasFailedSending, + roomType = roomSummaryEntity.roomType ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt new file mode 100644 index 0000000000..9dee99d7fe --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt @@ -0,0 +1,34 @@ +/* + * 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.database.mapper + +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity +import javax.inject.Inject + +internal class SpaceSummaryMapper @Inject constructor(private val roomSummaryMapper: RoomSummaryMapper) { + + fun map(spaceSummaryEntity: SpaceSummaryEntity): SpaceSummary { + return SpaceSummary( + spaceId = spaceSummaryEntity.spaceId, + roomSummary = roomSummaryMapper.map(spaceSummaryEntity.roomSummaryEntity!!), + children = spaceSummaryEntity.children.map { + roomSummaryMapper.map(it) + } + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 6e6096cf8a..8c5bb8e990 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -61,6 +61,7 @@ import io.realm.annotations.RealmModule CurrentStateEventEntity::class, UserAccountDataEntity::class, ScalarTokenEntity::class, - WellknownIntegrationManagerConfigEntity::class + WellknownIntegrationManagerConfigEntity::class, + SpaceSummaryEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt new file mode 100644 index 0000000000..ca54655022 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.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.database.model + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +internal open class SpaceSummaryEntity(@PrimaryKey var spaceId: String = "", + var roomSummaryEntity: RoomSummaryEntity? = null, + var children: RealmList = RealmList() + // TODO public / private .. and more +) : RealmObject() { + + // Do we want to denormalize that ? + +// private var membershipStr: String = Membership.NONE.name +// var membership: Membership +// get() { +// return Membership.valueOf(membershipStr) +// } +// set(value) { +// membershipStr = value.name +// } + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt new file mode 100644 index 0000000000..b6403c596f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt @@ -0,0 +1,55 @@ +/* + * 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.database.query + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields + +internal fun SpaceSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { + val query = realm.where() + query.isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`) + if (roomId != null) { + query.equalTo(SpaceSummaryEntityFields.SPACE_ID, roomId) + } + query.sort(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME) + return query +} + +internal fun SpaceSummaryEntity.Companion.findByAlias(realm: Realm, roomAlias: String): SpaceSummaryEntity? { + val spaceSummary = realm.where() + .isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`) + .equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, roomAlias) + .findFirst() + if (spaceSummary != null) { + return spaceSummary + } + return realm.where() + .isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`) + .contains(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.FLAT_ALIASES, "|$roomAlias") + .findFirst() +} + +internal fun SpaceSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): SpaceSummaryEntity { + return where(realm, roomId).findFirst() ?: realm.createObject(roomId).also { + it.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 821a9cba8c..ecb680c691 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.session.search.SearchService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService +import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.sync.FilterService import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService @@ -121,7 +122,8 @@ internal class DefaultSession @Inject constructor( private val thirdPartyService: Lazy, private val callSignalingService: Lazy, @UnauthenticatedWithCertificate - private val unauthenticatedWithCertificateOkHttpClient: Lazy + private val unauthenticatedWithCertificateOkHttpClient: Lazy, + private val spaceService: Lazy ) : Session, RoomService by roomService.get(), RoomDirectoryService by roomDirectoryService.get(), @@ -265,6 +267,8 @@ internal class DefaultSession @Inject constructor( override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get() + override fun spaceService(): SpaceService = spaceService.get() + override fun getOkHttpClient(): OkHttpClient { return unauthenticatedWithCertificateOkHttpClient.get() } 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 5133f72932..8f3445bec3 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 @@ -24,6 +24,7 @@ import org.commonmark.renderer.html.HtmlRenderer import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomService +import org.matrix.android.sdk.api.session.space.SpaceService 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 @@ -89,6 +90,7 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask +import org.matrix.android.sdk.internal.session.space.DefaultSpaceService import retrofit2.Retrofit @Module @@ -135,6 +137,9 @@ internal abstract class RoomModule { @Binds abstract fun bindRoomService(service: DefaultRoomService): RoomService + @Binds + abstract fun bindSpaceService(service: DefaultSpaceService): SpaceService + @Binds abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt new file mode 100644 index 0000000000..4025861caa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt @@ -0,0 +1,50 @@ +/* + * 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.relationship + +import io.realm.Realm +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.space.model.SpaceChildContent +import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.query.whereType + +/** + * Relationship between rooms and spaces + * The intention is that rooms and spaces form a hierarchy, which clients can use to structure the user's room list into a tree view. + * The parent/child relationship can be expressed in one of two ways: + * - The admins of a space can advertise rooms and subspaces for their space by setting m.space.child state events. + * The state_key is the ID of a child room or space, and the content should contain a via key which gives + * a list of candidate servers that can be used to join the room. present: true key is included to distinguish from a deleted state event. + * + * - Separately, rooms can claim parents via the m.room.parent state event: + */ +internal class RoomRelationshipHelper(private val realm: Realm, + private val roomId: String +) { + + fun getDirectChildrenDescriptions(): List { + return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD) + .findAll() + .filter { ContentMapper.map(it.root?.content).toModel()?.present == true } + .mapNotNull { + // ContentMapper.map(it.root?.content).toModel() + it.roomId + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index dd3fbe04b2..576e7f4eba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -189,6 +189,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn) } } + + queryParams.excludeType.forEach { + query.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, it) + } return query } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 7913bf71a2..f8a3495aa2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -25,6 +25,8 @@ 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.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM @@ -36,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.getOrCreate @@ -46,6 +49,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper +import org.matrix.android.sdk.internal.session.room.relationship.RoomRelationshipHelper import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications import timber.log.Timber @@ -89,6 +93,10 @@ internal class RoomSummaryUpdater @Inject constructor( val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root + val roomCreateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CREATE, stateKey = "")?.root + + val roomType = ContentMapper.map(roomCreateEvent?.content).toModel()?.type + roomSummaryEntity.roomType = roomType // Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) @@ -152,6 +160,16 @@ internal class RoomSummaryUpdater @Inject constructor( crossSigningService.onUsersDeviceUpdate(otherRoomMembers) } } + + if (roomType == RoomType.SPACE) { + val spaceSummaryEntity = SpaceSummaryEntity.getOrCreate(realm, roomId) + spaceSummaryEntity.roomSummaryEntity = roomSummaryEntity + spaceSummaryEntity.children.clear() + spaceSummaryEntity.children.addAll( + RoomRelationshipHelper(realm, roomId).getDirectChildrenDescriptions() + .map { RoomSummaryEntity.getOrCreate(realm, roomId) } + ) + } } private fun RoomSummaryEntity.updateHasFailedSending() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt new file mode 100644 index 0000000000..ae71ee5cf2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -0,0 +1,38 @@ +/* + * 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.space + +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.Room +import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.api.session.space.model.SpaceChildContent + +class DefaultSpace(private val room: Room) : Space { + + override fun asRoom(): Room { + return room + } + + override suspend fun addRoom(roomId: String) { + asRoom().sendStateEvent( + eventType = EventType.STATE_SPACE_CHILD, + stateKey = roomId, + body = SpaceChildContent(present = true).toContent() + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt new file mode 100644 index 0000000000..3d9e7d7764 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.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.space + +import androidx.lifecycle.LiveData +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.space.CreateSpaceParams +import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.api.session.space.SpaceService +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.RoomGetter +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 +import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask +import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask +import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask +import org.matrix.android.sdk.internal.task.TaskExecutor +import javax.inject.Inject + +internal class DefaultSpaceService @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val createRoomTask: CreateRoomTask, + private val joinRoomTask: JoinRoomTask, + private val markAllRoomsReadTask: MarkAllRoomsReadTask, + private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, + private val roomIdByAliasTask: GetRoomIdByAliasTask, + private val deleteRoomAliasTask: DeleteRoomAliasTask, + private val roomGetter: RoomGetter, + private val spaceSummaryDataSource: SpaceSummaryDataSource, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, + private val taskExecutor: TaskExecutor +) : SpaceService { + + override suspend fun createSpace(params: CreateSpaceParams): String { + return createRoomTask.execute(params) + } + + override fun getSpace(spaceId: String): Space? { + return roomGetter.getRoom(spaceId) + ?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE } + ?.let { DefaultSpace(it) } + } + + override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { + return spaceSummaryDataSource.getRoomSummariesLive(queryParams) + } + + override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { + return spaceSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt new file mode 100644 index 0000000000..c15e81c287 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt @@ -0,0 +1,93 @@ +/* + * 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.space + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.model.VersioningState +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.mapper.SpaceSummaryMapper +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.findByAlias +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.process +import org.matrix.android.sdk.internal.util.fetchCopyMap +import javax.inject.Inject + +internal class SpaceSummaryDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val spaceSummaryMapper: SpaceSummaryMapper +) { + + fun getSpaceSummary(roomIdOrAlias: String): SpaceSummary? { + return monarchy + .fetchCopyMap({ + if (roomIdOrAlias.startsWith("!")) { + // It's a roomId + SpaceSummaryEntity.where(it, roomId = roomIdOrAlias).findFirst() + } else { + // Assume it's a room alias + SpaceSummaryEntity.findByAlias(it, roomIdOrAlias) + } + }, { entity, _ -> + spaceSummaryMapper.map(entity) + }) + } + + fun getSpaceSummaryLive(roomId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm -> SpaceSummaryEntity.where(realm, roomId).isNotEmpty(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME) }, + { spaceSummaryMapper.map(it) } + ) + return Transformations.map(liveData) { results -> + results.firstOrNull().toOptional() + } + } + + fun getSpaceSummaries(queryParams: SpaceSummaryQueryParams): List { + return monarchy.fetchAllMappedSync( + { spaceSummariesQuery(it, queryParams) }, + { spaceSummaryMapper.map(it) } + ) + } + + fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { + return monarchy.findAllMappedWithChanges( + { spaceSummariesQuery(it, queryParams) }, + { spaceSummaryMapper.map(it) } + ) + } + + private fun spaceSummariesQuery(realm: Realm, queryParams: SpaceSummaryQueryParams): RealmQuery { + val query = SpaceSummaryEntity.where(realm) + query.process(SpaceSummaryEntityFields.SPACE_ID, queryParams.roomId) + query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME, queryParams.displayName) + query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, queryParams.canonicalAlias) + query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.MEMBERSHIP_STR, queryParams.memberships) + query.notEqualTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) + return query + } +} 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 430aee5468..1a3719e03f 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 @@ -52,6 +52,7 @@ import im.vector.app.features.devtools.RoomDevToolStateEventListFragment import im.vector.app.features.discovery.DiscoverySettingsFragment import im.vector.app.features.discovery.change.SetIdentityServerFragment import im.vector.app.features.grouplist.GroupListFragment +import im.vector.app.features.grouplist.SpaceListFragment import im.vector.app.features.home.HomeDetailFragment import im.vector.app.features.home.HomeDrawerFragment import im.vector.app.features.home.LoadingFragment @@ -145,6 +146,11 @@ interface FragmentModule { @FragmentKey(GroupListFragment::class) fun bindGroupListFragment(fragment: GroupListFragment): Fragment + @Binds + @IntoMap + @FragmentKey(SpaceListFragment::class) + fun bindSpaceListFragment(fragment: SpaceListFragment): Fragment + @Binds @IntoMap @FragmentKey(RoomDetailFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index 4b88ff6767..3a197d3f83 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -35,6 +35,7 @@ import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.crypto.keysrequest.KeyRequestHandler import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler import im.vector.app.features.grouplist.SelectedGroupDataSource +import im.vector.app.features.grouplist.SelectedSpaceDataSource import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider @@ -115,6 +116,8 @@ interface VectorComponent { fun selectedGroupStore(): SelectedGroupDataSource + fun selectedSpaceStore(): SelectedSpaceDataSource + fun roomDetailPendingActionStore(): RoomDetailPendingActionStore fun activeSessionObservableStore(): ActiveSessionDataSource diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 66d88f149a..b29d061dfb 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -46,7 +46,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d PLAIN("/plain", "", R.string.command_description_plain), DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session), CONFETTI("/confetti", "", R.string.command_confetti), - SNOW("/snow", "", R.string.command_snow); + SNOW("/snow", "", R.string.command_snow), + CREATE_SPACE("/createspace", " *", R.string.command_description_create_space); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index d458751364..57466ddf98 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -300,6 +300,18 @@ object CommandParser { val message = textMessage.substring(Command.SNOW.command.length).trim() ParsedCommand.SendChatEffect(ChatEffect.SNOW, message) } + Command.CREATE_SPACE.command -> { + val rawCommand = textMessage.substring(Command.CREATE_SPACE.command.length).trim() + val split = rawCommand.split(" ").map { it.trim() } + if (split.isEmpty()) { + ParsedCommand.ErrorSyntax(Command.CREATE_SPACE) + } else { + ParsedCommand.CreateSpace( + split[0], + split.subList(1, split.size) + ) + } + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index d17faeafb8..1017b29234 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -57,4 +57,5 @@ sealed class ParsedCommand { class SendPoll(val question: String, val options: List) : ParsedCommand() object DiscardSession : ParsedCommand() class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand() + class CreateSpace(val name: String, val invitees: List) : ParsedCommand() } diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt index 4b187f83ca..0f6f77783d 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import io.reactivex.Observable import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams @@ -96,8 +97,10 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state -> if (state.selectedGroup?.groupId != action.groupSummary.groupId) { // We take care of refreshing group data when selecting to be sure we get all the rooms and users - viewModelScope.launch { - session.getGroup(action.groupSummary.groupId)?.fetchGroupData() + tryOrNull { + viewModelScope.launch { + session.getGroup(action.groupSummary.groupId)?.fetchGroupData() + } } setState { copy(selectedGroup = action.groupSummary) } } diff --git a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt new file mode 100644 index 0000000000..ade86a9d89 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt @@ -0,0 +1,62 @@ +/* + * 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.grouplist + +import android.content.res.Resources +import android.util.TypedValue +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +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.platform.CheckableConstraintLayout + +@EpoxyModelClass(layout = R.layout.item_space) +abstract class HomeSpaceSummaryItem : VectorEpoxyModel() { + + @EpoxyAttribute var selected: Boolean = false + @EpoxyAttribute var listener: (() -> Unit)? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.rootView.setOnClickListener { listener?.invoke() } + holder.groupNameView.text = holder.view.context.getString(R.string.group_details_home) + holder.rootView.isChecked = selected + holder.rootView.context.resources + holder.avatarImageView.background = ContextCompat.getDrawable(holder.view.context, R.drawable.space_home_background) + holder.avatarImageView.setImageResource(R.drawable.ic_space_home) + holder.avatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE + } + + class Holder : VectorEpoxyHolder() { + val avatarImageView by bind(R.id.groupAvatarImageView) + val groupNameView by bind(R.id.groupNameView) + val rootView by bind(R.id.itemGroupLayout) + } + + fun dpToPx(resources: Resources, dp: Int): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + resources.displayMetrics + ).toInt() + } +} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt b/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt new file mode 100644 index 0000000000..d95251c271 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt @@ -0,0 +1,26 @@ +/* + * 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.grouplist + +import arrow.core.Option +import im.vector.app.core.utils.BehaviorDataSource +import org.matrix.android.sdk.api.session.space.SpaceSummary +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SelectedSpaceDataSource @Inject constructor() : BehaviorDataSource>(Option.empty()) diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt new file mode 100644 index 0000000000..4c783cb2d4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt @@ -0,0 +1,84 @@ +/* + * 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.grouplist + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +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.StateView +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentGroupListBinding +import im.vector.app.features.home.HomeActivitySharedAction +import im.vector.app.features.home.HomeSharedActionViewModel +import im.vector.app.features.spaces.SpaceListAction +import im.vector.app.features.spaces.SpaceListViewEvents +import im.vector.app.features.spaces.SpacesListViewModel +import org.matrix.android.sdk.api.session.space.SpaceSummary +import javax.inject.Inject + +class SpaceListFragment @Inject constructor( + val spaceListViewModelFactory: SpacesListViewModel.Factory, + private val spaceController: SpaceSummaryController +) : VectorBaseFragment(), SpaceSummaryController.Callback { + + private lateinit var sharedActionViewModel: HomeSharedActionViewModel + private val viewModel: SpacesListViewModel by fragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGroupListBinding { + return FragmentGroupListBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) + spaceController.callback = this + views.stateView.contentView = views.groupListView + views.groupListView.configureWith(spaceController) + viewModel.observeViewEvents { + when (it) { + is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) + }.exhaustive + } + } + + override fun onDestroyView() { + spaceController.callback = null + views.groupListView.cleanup() + super.onDestroyView() + } + + override fun invalidate() = withState(viewModel) { state -> + when (state.asyncSpaces) { + is Incomplete -> views.stateView.state = StateView.State.Loading + is Success -> views.stateView.state = StateView.State.Content + } + spaceController.update(state) + } + + override fun onSpaceSelected(spaceSummary: SpaceSummary) { + viewModel.handle(SpaceListAction.SelectSpace(spaceSummary)) + } +} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt new file mode 100644 index 0000000000..c7a27266fd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt @@ -0,0 +1,76 @@ +/* + * 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.grouplist + +import com.airbnb.epoxy.EpoxyController +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.spaces.SpaceListViewState +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class SpaceSummaryController @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider) : EpoxyController() { + + var callback: Callback? = null + private var viewState: SpaceListViewState? = null + + init { + requestModelBuild() + } + + fun update(viewState: SpaceListViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val nonNullViewState = viewState ?: return + buildGroupModels(nonNullViewState.asyncSpaces(), nonNullViewState.selectedSpace) + } + + private fun buildGroupModels(summaries: List?, selected: SpaceSummary?) { + if (summaries.isNullOrEmpty()) { + return + } + summaries.forEach { groupSummary -> + val isSelected = groupSummary.spaceId == selected?.spaceId + if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { + homeSpaceSummaryItem { + id(groupSummary.spaceId) + selected(isSelected) + listener { callback?.onSpaceSelected(groupSummary) } + } + } else { + spaceSummaryItem { + avatarRenderer(avatarRenderer) + id(groupSummary.spaceId) + matrixItem(groupSummary.toMatrixItem()) + selected(isSelected) + listener { callback?.onSpaceSelected(groupSummary) } + } + } + } + } + + interface Callback { + fun onSpaceSelected(spaceSummary: SpaceSummary) + } +} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt new file mode 100644 index 0000000000..1a710b764c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt @@ -0,0 +1,57 @@ +/* + * 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.grouplist + +import android.widget.ImageView +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.platform.CheckableConstraintLayout +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass(layout = R.layout.item_space) +abstract class SpaceSummaryItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var selected: Boolean = false + @EpoxyAttribute var listener: (() -> Unit)? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.rootView.setOnClickListener { listener?.invoke() } + holder.groupNameView.text = matrixItem.displayName + holder.rootView.isChecked = selected + avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) + } + + override fun unbind(holder: Holder) { + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val avatarImageView by bind(R.id.groupAvatarImageView) + val groupNameView by bind(R.id.groupNameView) + val rootView by bind(R.id.itemGroupLayout) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 1d673a2a07..1765372548 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -35,6 +35,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideRequest import im.vector.app.core.glide.GlideRequests +import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.ColorFilterTransformation @@ -48,7 +49,8 @@ import javax.inject.Inject */ class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, - private val matrixItemColorProvider: MatrixItemColorProvider) { + private val matrixItemColorProvider: MatrixItemColorProvider, + private val dimensionConverter: DimensionConverter) { companion object { private const val THUMBNAIL_SIZE = 250 @@ -61,6 +63,23 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active DrawableImageViewTarget(imageView)) } + @UiThread + fun renderSpace(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) { + val placeholder = getSpacePlaceholderDrawable(matrixItem) + val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) + glideRequests + .load(resolvedUrl) + .transform(MultiTransformation(CenterCrop(), RoundedCorners(dimensionConverter.dpToPx(8)))) + .placeholder(placeholder) + .into(DrawableImageViewTarget(imageView)) + } + + fun renderSpace(matrixItem: MatrixItem, imageView: ImageView) { + renderSpace( + matrixItem, + imageView, GlideApp.with(imageView)) + } + fun clear(imageView: ImageView) { // It can be called after recycler view is destroyed, just silently catch tryOrNull { GlideApp.with(imageView).clear(imageView) } @@ -159,6 +178,16 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active .buildRound(matrixItem.firstLetterOfDisplayName(), avatarColor) } + @AnyThread + fun getSpacePlaceholderDrawable(matrixItem: MatrixItem): Drawable { + val avatarColor = matrixItemColorProvider.getColor(matrixItem) + return TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRoundRect(matrixItem.firstLetterOfDisplayName(), avatarColor, dimensionConverter.dpToPx(8)) + } + // PRIVATE API ********************************************************************************* private fun buildGlideRequest(glideRequests: GlideRequests, avatarUrl: String?): GlideRequest { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 5def43b60b..de24be1a7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -22,7 +22,10 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.ImageView import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.lifecycle.Observer import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -47,11 +50,13 @@ import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS +import im.vector.app.features.spaces.ALL_COMMUNITIES_GROUP_ID import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.BannerState import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import org.matrix.android.sdk.api.session.group.model.GroupSummary +import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import timber.log.Timber @@ -130,6 +135,11 @@ class HomeDetailFragment @Inject constructor( viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary -> onGroupChange(groupSummary.orNull()) } + + viewModel.selectSubscribe(this, HomeDetailViewState::spaceSummary) { spaceSummary -> + onSpaceChange(spaceSummary.orNull()) + } + viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode -> switchDisplayMode(displayMode) } @@ -243,6 +253,24 @@ class HomeDetailFragment @Inject constructor( } } + private fun onSpaceChange(spaceSummary: SpaceSummary?) { + spaceSummary?.let { + // Use GlideApp with activity context to avoid the glideRequests to be paused + if (spaceSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { + // Special case + views.groupToolbarAvatarImageView.background = ContextCompat.getDrawable(requireContext(), R.drawable.space_home_background) + views.groupToolbarAvatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE + views.groupToolbarAvatarImageView.setImageResource(R.drawable.ic_space_home) + views.groupToolbarSpaceTitleView.isVisible = false + } else { + views.groupToolbarAvatarImageView.background = null + avatarRenderer.renderSpace(it.toMatrixItem(), views.groupToolbarAvatarImageView, GlideApp.with(requireActivity())) + views.groupToolbarSpaceTitleView.isVisible = true + views.groupToolbarSpaceTitleView.text = spaceSummary.displayName + } + } + } + private fun setupKeysBackupBanner() { serverBackupStatusViewModel .subscribe(this) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index d6a8b075f4..a07a329a57 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -28,6 +28,7 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.grouplist.SelectedGroupDataSource +import im.vector.app.features.grouplist.SelectedSpaceDataSource import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -75,6 +76,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho init { observeSyncState() observeSelectedGroupStore() + observeSelectedSpaceStore() observeRoomSummaries() } @@ -137,6 +139,17 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho .disposeOnClear() } + private fun observeSelectedSpaceStore() { + selectedSpaceStore + .observe() + .subscribe { + setState { + copy(spaceSummary = it) + } + } + .disposeOnClear() + } + private fun observeRoomSummaries() { session.getPagedRoomSummariesLive( roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index 533c9166f9..dd316dcece 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -22,10 +22,12 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.sync.SyncState data class HomeDetailViewState( val groupSummary: Option = Option.empty(), + val spaceSummary: Option = Option.empty(), val asyncRooms: Async> = Uninitialized, val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE, val notificationCountCatchup: Int = 0, diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index 59eb45607e..92be20367a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.databinding.FragmentHomeDrawerBinding import im.vector.app.features.grouplist.GroupListFragment +import im.vector.app.features.grouplist.SpaceListFragment import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.usercode.UserCodeActivity @@ -58,7 +59,11 @@ class HomeDrawerFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) if (savedInstanceState == null) { - replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java) + if (vectorPreferences.labSpaces()) { + replaceChildFragment(R.id.homeDrawerGroupListContainer, SpaceListFragment::class.java) + } else { + replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java) + } } session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser -> val user = optionalUser?.getOrNull() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 006a2c9b5f..bdf38719e2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -97,6 +97,8 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent +import org.matrix.android.sdk.api.session.space.CreateSpaceParams +import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.api.util.toOptional @@ -821,6 +823,22 @@ class RoomDetailViewModel @AssistedInject constructor( ) } } + is ParsedCommand.CreateSpace -> { + viewModelScope.launch(Dispatchers.IO) { + try { + val params = CreateSpaceParams().apply { + name = slashCommandResult.name + invitedUserIds.addAll(slashCommandResult.invitees) + } + val spaceId = session.spaceService().createSpace(params) + session.spaceService().getSpace(spaceId)?.addRoom(state.roomId) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) + } + } + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) + popDraft() + } }.exhaustive } is SendMode.EDIT -> { 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 94b419797d..e08f383512 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 @@ -89,19 +89,19 @@ class CreateRoomController @Inject constructor( enabled(enableFormElement) title(stringProvider.getString(R.string.create_room_public_title)) summary(stringProvider.getString(R.string.create_room_public_description)) - switchChecked(viewState.roomType is CreateRoomViewState.RoomType.Public) - showDivider(viewState.roomType !is CreateRoomViewState.RoomType.Public) + switchChecked(viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) + showDivider(viewState.roomVisibilityType !is CreateRoomViewState.RoomVisibilityType.Public) listener { value -> listener?.setIsPublic(value) } } - if (viewState.roomType is CreateRoomViewState.RoomType.Public) { + if (viewState.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) { // Room alias for public room roomAliasEditItem { id("alias") enabled(enableFormElement) - value(viewState.roomType.aliasLocalPart) + value(viewState.roomVisibilityType.aliasLocalPart) homeServer(":" + viewState.homeServerName) errorMessage( roomAliasErrorFormatter.format( diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index af63f23a8c..33dc6bc054 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -76,7 +76,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr setState { copy( - isEncrypted = roomType is CreateRoomViewState.RoomType.Private && adminE2EByDefault, + isEncrypted = roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Private && adminE2EByDefault, hsAdminHasDisabledE2E = !adminE2EByDefault ) } @@ -147,14 +147,14 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState { if (action.isPublic) { copy( - roomType = CreateRoomViewState.RoomType.Public(""), + roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(""), // Reset any error in the form about alias asyncCreateRoomRequest = Uninitialized, isEncrypted = false ) } else { copy( - roomType = CreateRoomViewState.RoomType.Private, + roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Private, isEncrypted = adminE2EByDefault ) } @@ -162,10 +162,10 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) { withState { state -> - if (state.roomType is CreateRoomViewState.RoomType.Public) { + if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public) { setState { copy( - roomType = CreateRoomViewState.RoomType.Public(action.aliasLocalPart), + roomVisibilityType = CreateRoomViewState.RoomVisibilityType.Public(action.aliasLocalPart), // Reset any error in the form about alias asyncCreateRoomRequest = Uninitialized ) @@ -191,15 +191,15 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr name = state.roomName.takeIf { it.isNotBlank() } topic = state.roomTopic.takeIf { it.isNotBlank() } avatarUri = state.avatarUri - when (state.roomType) { - is CreateRoomViewState.RoomType.Public -> { + when (state.roomVisibilityType) { + is CreateRoomViewState.RoomVisibilityType.Public -> { // Directory visibility visibility = RoomDirectoryVisibility.PUBLIC // Preset preset = CreateRoomPreset.PRESET_PUBLIC_CHAT - roomAliasName = state.roomType.aliasLocalPart + roomAliasName = state.roomVisibilityType.aliasLocalPart } - is CreateRoomViewState.RoomType.Private -> { + is CreateRoomViewState.RoomVisibilityType.Private -> { // Directory visibility visibility = RoomDirectoryVisibility.PRIVATE // Preset diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt index 4609693c8f..6bc19dfa20 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -26,7 +26,7 @@ data class CreateRoomViewState( val avatarUri: Uri? = null, val roomName: String = "", val roomTopic: String = "", - val roomType: RoomType = RoomType.Private, + val roomVisibilityType: RoomVisibilityType = RoomVisibilityType.Private, val isEncrypted: Boolean = false, val showAdvanced: Boolean = false, val disableFederation: Boolean = false, @@ -45,10 +45,10 @@ data class CreateRoomViewState( fun isEmpty() = avatarUri == null && roomName.isEmpty() && roomTopic.isEmpty() - && (roomType as? RoomType.Public)?.aliasLocalPart?.isEmpty().orTrue() + && (roomVisibilityType as? RoomVisibilityType.Public)?.aliasLocalPart?.isEmpty().orTrue() - sealed class RoomType { - object Private : RoomType() - data class Public(val aliasLocalPart: String) : RoomType() + sealed class RoomVisibilityType { + object Private : RoomVisibilityType() + data class Public(val aliasLocalPart: String) : RoomVisibilityType() } } 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 9b043cfc7c..222c8da6b7 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 @@ -156,6 +156,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY" + const val SETTINGS_LABS_USE_SPACES = "SETTINGS_LABS_USE_SPACES" + // SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS private const val SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM = "SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM" const val SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB = "SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" @@ -306,6 +308,10 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB, false) } + fun labSpaces(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_USE_SPACES, false) + } + fun failFast(): Boolean { return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt new file mode 100644 index 0000000000..5daedcc984 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -0,0 +1,172 @@ +/* + * 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.spaces + +import arrow.core.Option +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +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.R +import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.grouplist.SelectedSpaceDataSource +import im.vector.app.features.grouplist.SpaceListFragment +import io.reactivex.Observable +import io.reactivex.functions.BiFunction +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.rx.rx + +const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" + +sealed class SpaceListAction : VectorViewModelAction { + data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction() +} + +/** + * Transient events for group list screen + */ +sealed class SpaceListViewEvents : VectorViewEvents { + object OpenSpaceSummary : SpaceListViewEvents() +} + +data class SpaceListViewState( + val asyncSpaces: Async> = Uninitialized, + val selectedSpace: SpaceSummary? = null +) : MvRxState + +class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, + private val selectedSpaceDataSource: SelectedSpaceDataSource, + private val session: Session, + private val stringProvider: StringProvider +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: SpaceListViewState): SpacesListViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: SpaceListViewState): SpacesListViewModel { + val groupListFragment: SpaceListFragment = (viewModelContext as FragmentViewModelContext).fragment() + return groupListFragment.spaceListViewModelFactory.create(state) + } + } + + private var currentGroupId = "" + + init { + observeGroupSummaries() + observeSelectionState() + } + + private fun observeSelectionState() { + selectSubscribe(SpaceListViewState::selectedSpace) { spaceSummary -> + if (spaceSummary != null) { + // We only want to open group if the updated selectedGroup is a different one. + if (currentGroupId != spaceSummary.spaceId) { + currentGroupId = spaceSummary.spaceId + _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary) + } + val optionGroup = Option.just(spaceSummary) + selectedSpaceDataSource.post(optionGroup) + } else { + // If selected group is null we force to default. It can happens when leaving the selected group. + setState { + copy(selectedSpace = this.asyncSpaces()?.find { it.spaceId == ALL_COMMUNITIES_GROUP_ID }) + } + } + } + } + + override fun handle(action: SpaceListAction) { + when (action) { + is SpaceListAction.SelectSpace -> handleSelectSpace(action) + } + } + + // PRIVATE METHODS ***************************************************************************** + + private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> + if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) { + // We take care of refreshing group data when selecting to be sure we get all the rooms and users +// tryOrNull { +// viewModelScope.launch { +// session.getGroup(action.spaceSummary.groupId)?.fetchGroupData() +// } +// } + setState { copy(selectedSpace = action.spaceSummary) } + } + } + + private fun observeGroupSummaries() { + val roomSummaryQueryParams = roomSummaryQueryParams() { + memberships = listOf(Membership.JOIN) + displayName = QueryStringValue.IsNotEmpty + excludeType = listOf(RoomType.MESSAGING, null) + } + Observable.combineLatest, List>( + session + .rx() + .liveUser(session.myUserId) + .map { optionalUser -> + SpaceSummary( + spaceId = ALL_COMMUNITIES_GROUP_ID, + roomSummary = RoomSummary( + roomId = ALL_COMMUNITIES_GROUP_ID, + membership = Membership.JOIN, + displayName = stringProvider.getString(R.string.group_all_communities), + avatarUrl = optionalUser.getOrNull()?.avatarUrl ?: "", + encryptionEventTs = 0, + isEncrypted = false, + typingUsers = emptyList() + ), + children = emptyList() + ) + }, + session + .rx() + .liveSpaceSummaries(roomSummaryQueryParams), + BiFunction { allCommunityGroup, communityGroups -> + listOf(allCommunityGroup) + communityGroups + } + ) + .execute { async -> + val currentSelectedGroupId = selectedSpace?.spaceId + val newSelectedGroup = if (currentSelectedGroupId != null) { + async()?.find { it.spaceId == currentSelectedGroupId } + } else { + async()?.firstOrNull() + } + copy(asyncSpaces = async, selectedSpace = newSelectedGroup) + } + } +} diff --git a/vector/src/main/res/drawable/bg_group_item.xml b/vector/src/main/res/drawable/bg_group_item.xml index 9e48ebc725..ea39f5a9d0 100644 --- a/vector/src/main/res/drawable/bg_group_item.xml +++ b/vector/src/main/res/drawable/bg_group_item.xml @@ -3,7 +3,7 @@ - + diff --git a/vector/src/main/res/drawable/bg_space_item.xml b/vector/src/main/res/drawable/bg_space_item.xml new file mode 100644 index 0000000000..1cb879a0ca --- /dev/null +++ b/vector/src/main/res/drawable/bg_space_item.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/ic_selected_community.xml b/vector/src/main/res/drawable/ic_selected_community.xml new file mode 100644 index 0000000000..e95b54aab3 --- /dev/null +++ b/vector/src/main/res/drawable/ic_selected_community.xml @@ -0,0 +1,9 @@ + + + diff --git a/vector/src/main/res/drawable/ic_space_home.xml b/vector/src/main/res/drawable/ic_space_home.xml new file mode 100644 index 0000000000..e5935156f4 --- /dev/null +++ b/vector/src/main/res/drawable/ic_space_home.xml @@ -0,0 +1,12 @@ + + + diff --git a/vector/src/main/res/drawable/space_home_background.xml b/vector/src/main/res/drawable/space_home_background.xml new file mode 100644 index 0000000000..ec51c30a20 --- /dev/null +++ b/vector/src/main/res/drawable/space_home_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index d25375f3b9..ba2b630efe 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -22,24 +22,49 @@ android:orientation="horizontal"> - + android:layout_weight="1" + android:gravity="start" + android:orientation="vertical" + android:paddingStart="8dp" + android:paddingEnd="8dp"> + + + + + + + @@ -60,10 +85,10 @@ android:background="?riotx_keys_backup_banner_accent_color" android:minHeight="67dp" android:visibility="gone" - tools:visibility="visible" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/syncStateView" /> + app:layout_constraintTop_toBottomOf="@id/syncStateView" + tools:visibility="visible" /> + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 634b91bf90..c9cb4729f7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2183,6 +2183,8 @@ Enable swipe to reply in timeline Add a dedicated tab for unread notifications on main screen. + Enable Spaces (formerly known as ‘groups as rooms’) to allow users to organise rooms into more useful groups. + Link copied to clipboard Add by matrix ID @@ -3242,6 +3244,8 @@ State event sent! Event content + Create a community + Sending Sent Failed diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index fef5a2fe9d..ed1ea222a2 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -45,4 +45,10 @@ android:title="@string/labs_show_unread_notifications_as_tab" /> + + + \ No newline at end of file From df341d8ea38a1d8a9cab1432e92db999163fd608 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 7 Jan 2021 10:43:00 +0100 Subject: [PATCH 095/230] Basic peeking preview and join and filter --- .../sdk/api/session/events/model/EventType.kt | 1 + .../api/session/room/peeking/PeekResult.kt | 2 + .../android/sdk/api/session/space/Space.kt | 2 + .../sdk/api/session/space/SpaceService.kt | 27 +++ .../session/space/model/SpaceChildContent.kt | 1 + .../sdk/internal/session/room/RoomModule.kt | 5 + .../relationship/RoomRelationshipHelper.kt | 2 +- .../room/summary/RoomSummaryUpdater.kt | 2 +- .../internal/session/space/DefaultSpace.kt | 8 + .../session/space/DefaultSpaceService.kt | 30 ++++ .../session/space/peeking/PeekSpaceTask.kt | 141 +++++++++++++++ .../session/space/peeking/SpacePeekResult.kt | 55 ++++++ vector/src/main/AndroidManifest.xml | 3 +- .../im/vector/app/core/di/FragmentModule.kt | 6 + .../im/vector/app/core/di/ViewModelModule.kt | 6 + .../im/vector/app/features/command/Command.kt | 3 +- .../app/features/command/CommandParser.kt | 8 +- .../app/features/command/ParsedCommand.kt | 1 + .../features/grouplist/SpaceListFragment.kt | 3 +- .../grouplist/SpaceSummaryController.kt | 69 ++++++-- .../vector/app/features/home/HomeActivity.kt | 4 + .../features/home/HomeActivitySharedAction.kt | 1 + .../home/room/detail/RoomDetailViewModel.kt | 11 ++ .../features/spaces/SpacePreviewActivity.kt | 72 ++++++++ .../SpacePreviewSharedActionViewModel.kt | 30 ++++ .../features/spaces/SpacesListViewModel.kt | 26 ++- .../features/spaces/preview/RoomChildItem.kt | 79 +++++++++ .../spaces/preview/SpacePreviewController.kt | 117 +++++++++++++ .../spaces/preview/SpacePreviewFragment.kt | 165 ++++++++++++++++++ .../spaces/preview/SpacePreviewState.kt | 31 ++++ .../spaces/preview/SpacePreviewViewAction.kt | 25 +++ .../spaces/preview/SpacePreviewViewEvents.kt | 26 +++ .../spaces/preview/SpacePreviewViewModel.kt | 135 ++++++++++++++ .../features/spaces/preview/SpaceTabView.kt | 51 ++++++ .../spaces/preview/SpaceTopSummaryItem.kt | 46 +++++ .../features/spaces/preview/SubSpaceItem.kt | 68 ++++++++ .../res/layout/fragment_space_preview.xml | 127 ++++++++++++++ .../main/res/layout/item_space_roomchild.xml | 115 ++++++++++++ .../main/res/layout/item_space_subspace.xml | 71 ++++++++ vector/src/main/res/layout/item_space_tab.xml | 11 ++ .../res/layout/item_space_top_summary.xml | 46 +++++ vector/src/main/res/values/strings.xml | 3 + 42 files changed, 1604 insertions(+), 31 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/SpacePreviewSharedActionViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/RoomChildItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTopSummaryItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt create mode 100644 vector/src/main/res/layout/fragment_space_preview.xml create mode 100644 vector/src/main/res/layout/item_space_roomchild.xml create mode 100644 vector/src/main/res/layout/item_space_subspace.xml create mode 100644 vector/src/main/res/layout/item_space_tab.xml create mode 100644 vector/src/main/res/layout/item_space_top_summary.xml 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 9d8f18e912..4be8eea856 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 @@ -52,6 +52,7 @@ object EventType { const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" const val STATE_SPACE_CHILD = "m.space.child" +// const val STATE_SPACE_CHILD = "org.matrix.msc1772.space" /** * Note that this Event has been deprecated, see diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt index db70dadef3..a27e88aced 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt @@ -34,4 +34,6 @@ sealed class PeekResult { ) : PeekResult() object UnknownAlias : PeekResult() + + fun isSuccess() = this is Success } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 75480282fa..88ac00cc55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -23,4 +23,6 @@ interface Space { fun asRoom() : Room suspend fun addRoom(roomId: String) + +// fun getChildren() : List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 0c3461f1ca..4043a3f7b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult typealias SpaceSummaryQueryParams = RoomSummaryQueryParams @@ -35,6 +36,13 @@ interface SpaceService { */ fun getSpace(spaceId: String): Space? + /** + * Try to resolve (peek) rooms and subspace in this space. + * Use this call get preview of children of this space, particularly useful to get a + * preview of rooms that you did not join yet. + */ + suspend fun peekSpace(spaceId: String) : SpacePeekResult + /** * Get a live list of space summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[SpaceSummary] @@ -42,4 +50,23 @@ interface SpaceService { fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List + + data class ChildAutoJoinInfo( + val roomIdOrAlias: String, + val viaServers: List = emptyList() + ) + + sealed class JoinSpaceResult { + object Success: JoinSpaceResult() + data class Fail(val error: Throwable?): JoinSpaceResult() + /** Success fully joined the space, but failed to join all or some of it's rooms */ + data class PartialSuccess(val failedRooms: Map) : JoinSpaceResult() + + fun isSuccess() = this is Success || this is PartialSuccess + } + + suspend fun joinSpace(spaceIdOrAlias: String, + reason: String? = null, + viaServers: List = emptyList(), + autoJoinChild: List) : JoinSpaceResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt index f65318b543..f7bd067c55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -35,6 +35,7 @@ data class SpaceChildContent( @Json(name = "via") val via: List? = null, /** * present: true key is included to distinguish from a deleted state event + * Children where present is not present or is not set to true are ignored. */ @Json(name = "present") val present: Boolean? = false, /** 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 8f3445bec3..b7c4246eca 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 @@ -91,6 +91,8 @@ import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask import org.matrix.android.sdk.internal.session.space.DefaultSpaceService +import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask +import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import retrofit2.Retrofit @Module @@ -236,6 +238,9 @@ internal abstract class RoomModule { @Binds abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask + @Binds + abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask + @Binds abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt index 4025861caa..b1bcfc7077 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt @@ -44,7 +44,7 @@ internal class RoomRelationshipHelper(private val realm: Realm, .filter { ContentMapper.map(it.root?.content).toModel()?.present == true } .mapNotNull { // ContentMapper.map(it.root?.content).toModel() - it.roomId + it.stateKey } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index f8a3495aa2..7b637cc9e9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -167,7 +167,7 @@ internal class RoomSummaryUpdater @Inject constructor( spaceSummaryEntity.children.clear() spaceSummaryEntity.children.addAll( RoomRelationshipHelper(realm, roomId).getDirectChildrenDescriptions() - .map { RoomSummaryEntity.getOrCreate(realm, roomId) } + .map { RoomSummaryEntity.getOrCreate(realm, it) } ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index ae71ee5cf2..ebe845572d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -35,4 +35,12 @@ class DefaultSpace(private val room: Room) : Space { body = SpaceChildContent(present = true).toContent() ) } + +// override fun getChildren(): List { +// // asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD)).mapNotNull { +// // // statekeys are the roomIds +// // +// // } +// return emptyList() +// } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 3d9e7d7764..4118d74604 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -32,6 +32,8 @@ import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask +import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask +import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject @@ -46,6 +48,7 @@ internal class DefaultSpaceService @Inject constructor( private val deleteRoomAliasTask: DeleteRoomAliasTask, private val roomGetter: RoomGetter, private val spaceSummaryDataSource: SpaceSummaryDataSource, + private val peekSpaceTask: PeekSpaceTask, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val taskExecutor: TaskExecutor ) : SpaceService { @@ -67,4 +70,31 @@ internal class DefaultSpaceService @Inject constructor( override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { return spaceSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) } + + override suspend fun peekSpace(spaceId: String): SpacePeekResult { + return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId)) + } + + override suspend fun joinSpace(spaceIdOrAlias: String, reason: String?, viaServers: List, autoJoinChild: List): SpaceService.JoinSpaceResult { + try { + joinRoomTask.execute(JoinRoomTask.Params(spaceIdOrAlias, reason, viaServers)) + val childJoinFailures = mutableMapOf() + autoJoinChild.forEach { info -> + // TODO what if the child is it self a subspace with some default children? + try { + joinRoomTask.execute(JoinRoomTask.Params(info.roomIdOrAlias, null, info.viaServers)) + } catch (failure: Throwable) { + // TODO, i could already be a member of this room, handle that as it should not be an error in this context + childJoinFailures[info.roomIdOrAlias] = failure + } + } + return if (childJoinFailures.isEmpty()) { + SpaceService.JoinSpaceResult.Success + } else { + SpaceService.JoinSpaceResult.PartialSuccess(childJoinFailures) + } + } catch (throwable: Throwable) { + return SpaceService.JoinSpaceResult.Fail(throwable) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt new file mode 100644 index 0000000000..826be0b3aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt @@ -0,0 +1,141 @@ +/* + * 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 org.matrix.android.sdk.internal.session.space.peeking + +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.toModel +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.space.model.SpaceChildContent +import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask +import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask +import org.matrix.android.sdk.internal.task.Task +import timber.log.Timber +import javax.inject.Inject + +internal interface PeekSpaceTask : Task { + data class Params( + val roomIdOrAlias: String, + // A depth limit as a simple protection against cycles + val maxDepth: Int = 4 + ) +} + +internal class DefaultPeekSpaceTask @Inject constructor( + private val peekRoomTask: PeekRoomTask, + private val resolveRoomStateTask: ResolveRoomStateTask +) : PeekSpaceTask { + + override suspend fun execute(params: PeekSpaceTask.Params): SpacePeekResult { + val peekResult = peekRoomTask.execute(PeekRoomTask.Params(params.roomIdOrAlias)) + val roomResult = peekResult as? PeekResult.Success ?: return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult) + + // check the room type + // kind of duplicate cause we already did it in Peek? could we pass on the result?? + val stateEvents = try { + resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomResult.roomId)) + } catch (failure: Throwable) { + return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult) + } + val isSpace = stateEvents + .lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" } + ?.let { it.content?.toModel()?.type } == RoomType.SPACE + + if (!isSpace) return SpacePeekResult.NotSpaceType(params.roomIdOrAlias) + + val children = peekChildren(stateEvents, 0, params.maxDepth) + + return SpacePeekResult.Success( + SpacePeekSummary( + params.roomIdOrAlias, + peekResult, + children + ) + ) + } + + private suspend fun peekChildren(stateEvents: List, depth: Int, maxDepth: Int): List { + if (depth >= maxDepth) return emptyList() + val childRoomsIds = stateEvents + .filter { + it.type == EventType.STATE_SPACE_CHILD && !it.stateKey.isNullOrEmpty() + // Children where present is not present or is not set to true are ignored. + && it.content?.toModel()?.present == true + } + .map { it.stateKey to it.content?.toModel() } + + Timber.v("## SPACE_PEEK: found ${childRoomsIds.size} present children") + + val spaceChildResults = mutableListOf() + childRoomsIds.forEach { entry -> + + Timber.v("## SPACE_PEEK: peeking child $entry") + // peek each child + val childId = entry.first ?: return@forEach + try { + val childPeek = peekRoomTask.execute(PeekRoomTask.Params(childId)) + + val childStateEvents = resolveRoomStateTask.execute(ResolveRoomStateTask.Params(childId)) + val createContent = childStateEvents + .lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" } + ?.let { it.content?.toModel() } + + if (!childPeek.isSuccess() || createContent == null) { + Timber.v("## SPACE_PEEK: cannot peek child $entry") + // can't peek :/ + spaceChildResults.add( + SpaceChildPeekResult( + childId, childPeek, entry.second?.default, entry.second?.order + ) + ) + // continue to next child + return@forEach + } + val type = createContent.type + if (type == RoomType.SPACE) { + Timber.v("## SPACE_PEEK: subspace child $entry") + spaceChildResults.add( + SpaceSubChildPeekResult( + childId, + childPeek, + entry.second?.default, + entry.second?.order, + peekChildren(childStateEvents, depth + 1, maxDepth) + ) + ) + } else if (type == RoomType.MESSAGING || type == null) { + Timber.v("## SPACE_PEEK: room child $entry") + spaceChildResults.add( + SpaceChildPeekResult( + childId, childPeek, entry.second?.default, entry.second?.order + ) + ) + } else { + // ignore for now? + } + + // let's check child info + } catch (failure: Throwable) { + // can this happen? + Timber.e(failure, "## Failed to resolve space child") + } + } + return spaceChildResults + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt new file mode 100644 index 0000000000..63eed2a6c2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt @@ -0,0 +1,55 @@ +/* + * 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 org.matrix.android.sdk.internal.session.space.peeking + +import org.matrix.android.sdk.api.session.room.peeking.PeekResult + +data class SpacePeekSummary( + val idOrAlias: String, + val roomPeekResult: PeekResult.Success, + val children: List +) + +interface ISpaceChild { + val id: String + val roomPeekResult: PeekResult + val default: Boolean? + val order: String? +} + +data class SpaceChildPeekResult( + override val id: String, + override val roomPeekResult: PeekResult, + override val default: Boolean? = null, + override val order: String? = null +) : ISpaceChild + +data class SpaceSubChildPeekResult( + override val id: String, + override val roomPeekResult: PeekResult, + override val default: Boolean?, + override val order: String?, + val children: List +) : ISpaceChild + +sealed class SpacePeekResult { + abstract class SpacePeekError : SpacePeekResult() + data class FailedToResolve(val spaceId: String, val roomPeekResult: PeekResult) : SpacePeekError() + data class NotSpaceType(val spaceId: String) : SpacePeekError() + + data class Success(val summary: SpacePeekSummary): SpacePeekResult() +} diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 07606d315c..9a1f2e6dfd 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -271,7 +271,8 @@ - + + ", R.string.command_confetti), SNOW("/snow", "", R.string.command_snow), - CREATE_SPACE("/createspace", " *", R.string.command_description_create_space); + CREATE_SPACE("/createspace", " *", R.string.command_description_create_space), + ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index 57466ddf98..fe5707ec45 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -296,7 +296,7 @@ object CommandParser { val message = textMessage.substring(Command.CONFETTI.command.length).trim() ParsedCommand.SendChatEffect(ChatEffect.CONFETTI, message) } - Command.SNOW.command -> { + Command.SNOW.command -> { val message = textMessage.substring(Command.SNOW.command.length).trim() ParsedCommand.SendChatEffect(ChatEffect.SNOW, message) } @@ -312,6 +312,12 @@ object CommandParser { ) } } + Command.ADD_TO_SPACE.command -> { + val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim() + ParsedCommand.AddToSpace( + rawCommand + ) + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index 1017b29234..99b0ae7889 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -58,4 +58,5 @@ sealed class ParsedCommand { object DiscardSession : ParsedCommand() class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand() class CreateSpace(val name: String, val invitees: List) : ParsedCommand() + class AddToSpace(val spaceId: String) : ParsedCommand() } diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt index 4c783cb2d4..7091c0b86c 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt @@ -59,7 +59,8 @@ class SpaceListFragment @Inject constructor( views.groupListView.configureWith(spaceController) viewModel.observeViewEvents { when (it) { - is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) + is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id)) + is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) }.exhaustive } } diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt index c7a27266fd..dab2cbceae 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt @@ -18,9 +18,13 @@ package im.vector.app.features.grouplist import com.airbnb.epoxy.EpoxyController +import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.core.ui.list.genericItemHeader import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.spaces.SpaceListViewState +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -50,24 +54,57 @@ class SpaceSummaryController @Inject constructor( if (summaries.isNullOrEmpty()) { return } - summaries.forEach { groupSummary -> - val isSelected = groupSummary.spaceId == selected?.spaceId - if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { - homeSpaceSummaryItem { - id(groupSummary.spaceId) - selected(isSelected) - listener { callback?.onSpaceSelected(groupSummary) } + // show invites on top + + summaries.filter { it.roomSummary.membership == Membership.INVITE } + .let { invites -> + if (invites.isNotEmpty()) { + genericItemHeader { + id("invites") + text(stringProvider.getString(R.string.spaces_invited_header)) + } + invites.forEach { + spaceSummaryItem { + avatarRenderer(avatarRenderer) + id(it.spaceId) + matrixItem(it.toMatrixItem()) + selected(false) + listener { callback?.onSpaceSelected(it) } + } + } + genericFooterItem { + id("invite_space") + text("") + } + } } - } else { - spaceSummaryItem { - avatarRenderer(avatarRenderer) - id(groupSummary.spaceId) - matrixItem(groupSummary.toMatrixItem()) - selected(isSelected) - listener { callback?.onSpaceSelected(groupSummary) } - } - } + + genericItemHeader { + id("spaces") + text(stringProvider.getString(R.string.spaces_header)) } + + summaries + .filter { it.roomSummary.membership == Membership.JOIN } + .forEach { groupSummary -> + + val isSelected = groupSummary.spaceId == selected?.spaceId + if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { + homeSpaceSummaryItem { + id(groupSummary.spaceId) + selected(isSelected) + listener { callback?.onSpaceSelected(groupSummary) } + } + } else { + spaceSummaryItem { + avatarRenderer(avatarRenderer) + id(groupSummary.spaceId) + matrixItem(groupSummary.toMatrixItem()) + selected(isSelected) + listener { callback?.onSpaceSelected(groupSummary) } + } + } + } } interface Callback { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 60a8836be5..138ffc26f4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -54,6 +54,7 @@ import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity +import im.vector.app.features.spaces.SpacePreviewActivity import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState @@ -141,6 +142,9 @@ class HomeActivity : views.drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) } + is HomeActivitySharedAction.OpenSpacePreview -> { + startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId)) + } }.exhaustive } .disposeOnDestroy() diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt index 52b3c58785..f72354465b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt @@ -25,4 +25,5 @@ sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction() object OpenGroup : HomeActivitySharedAction() + data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index bdf38719e2..785449236c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -839,6 +839,17 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) popDraft() } + is ParsedCommand.AddToSpace -> { + viewModelScope.launch(Dispatchers.IO) { + try { + session.spaceService().getSpace(slashCommandResult.spaceId)?.addRoom(room.roomId) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) + } + } + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) + popDraft() + } }.exhaustive } is SendMode.EDIT -> { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt new file mode 100644 index 0000000000..dacde8846c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewActivity.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021 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.spaces + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.airbnb.mvrx.MvRx +import im.vector.app.R +import im.vector.app.core.extensions.commitTransaction +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding +import im.vector.app.features.spaces.preview.SpacePreviewArgs +import im.vector.app.features.spaces.preview.SpacePreviewFragment + +class SpacePreviewActivity : VectorBaseActivity() { + + lateinit var sharedActionViewModel: SpacePreviewSharedActionViewModel + + override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) + sharedActionViewModel + .observe() + .subscribe { action -> + when (action) { + SpacePreviewSharedAction.DismissAction -> finish() + SpacePreviewSharedAction.ShowModalLoading -> showWaitingView() + SpacePreviewSharedAction.HideModalLoading -> hideWaitingView() + is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) } + } + }.disposeOnDestroy() + + if (isFirstCreation()) { + val simpleName = SpacePreviewFragment::class.java.simpleName + val args = intent?.getParcelableExtra(MvRx.KEY_ARG) + if (supportFragmentManager.findFragmentByTag(simpleName) == null) { + supportFragmentManager.commitTransaction { + replace(R.id.simpleFragmentContainer, + SpacePreviewFragment::class.java, + Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) }, + simpleName + ) + } + } + } + } + + companion object { + fun newIntent(context: Context, spaceIdOrAlias: String): Intent { + return Intent(context, SpacePreviewActivity::class.java).apply { + putExtra(MvRx.KEY_ARG, SpacePreviewArgs(spaceIdOrAlias)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewSharedActionViewModel.kt new file mode 100644 index 0000000000..058b1a275b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacePreviewSharedActionViewModel.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 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.spaces + +import im.vector.app.core.platform.VectorSharedAction +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +sealed class SpacePreviewSharedAction : VectorSharedAction { + object DismissAction : SpacePreviewSharedAction() + object ShowModalLoading : SpacePreviewSharedAction() + object HideModalLoading : SpacePreviewSharedAction() + data class ShowErrorMessage(val error: String? = null) : SpacePreviewSharedAction() +} + +class SpacePreviewSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 5daedcc984..f0d8ae30f7 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -53,7 +53,8 @@ sealed class SpaceListAction : VectorViewModelAction { * Transient events for group list screen */ sealed class SpaceListViewEvents : VectorViewEvents { - object OpenSpaceSummary : SpaceListViewEvents() + object OpenSpace : SpaceListViewEvents() + data class OpenSpaceSummary(val id: String) : SpaceListViewEvents() } data class SpaceListViewState( @@ -94,7 +95,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp // We only want to open group if the updated selectedGroup is a different one. if (currentGroupId != spaceSummary.spaceId) { currentGroupId = spaceSummary.spaceId - _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary) + _viewEvents.post(SpaceListViewEvents.OpenSpace) } val optionGroup = Option.just(spaceSummary) selectedSpaceDataSource.post(optionGroup) @@ -116,20 +117,27 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp // PRIVATE METHODS ***************************************************************************** private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> - if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) { - // We take care of refreshing group data when selecting to be sure we get all the rooms and users -// tryOrNull { -// viewModelScope.launch { -// session.getGroup(action.spaceSummary.groupId)?.fetchGroupData() + + if (state.selectedSpace?.roomSummary?.membership == Membership.INVITE) { + _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary(state.selectedSpace.roomSummary.roomId)) +// viewModelScope.launch(Dispatchers.IO) { +// tryOrNull { session.spaceService().peekSpace(action.spaceSummary.spaceId) }.let { +// Timber.d("PEEK RESULT/ $it") // } // } - setState { copy(selectedSpace = action.spaceSummary) } + } else { + if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) { +// state.selectedSpace?.let { +// selectedSpaceDataSource.post(Option.just(state.selectedSpace)) +// } + setState { copy(selectedSpace = action.spaceSummary) } + } } } private fun observeGroupSummaries() { val roomSummaryQueryParams = roomSummaryQueryParams() { - memberships = listOf(Membership.JOIN) + memberships = listOf(Membership.JOIN, Membership.INVITE) displayName = QueryStringValue.IsNotEmpty excludeType = listOf(RoomType.MESSAGING, null) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/RoomChildItem.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/RoomChildItem.kt new file mode 100644 index 0000000000..bf28618c6c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/RoomChildItem.kt @@ -0,0 +1,79 @@ +/* + * 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.spaces.preview + +import android.widget.ImageView +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 +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass(layout = R.layout.item_space_roomchild) +abstract class RoomChildItem : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var roomId: String + + @EpoxyAttribute + lateinit var title: String + + @EpoxyAttribute + var topic: String? = null + + @EpoxyAttribute + lateinit var memberCount: String + + @EpoxyAttribute + var avatarUrl: String? = null + + @EpoxyAttribute + lateinit var avatarRenderer: AvatarRenderer + + @EpoxyAttribute + var depth: Int = 0 + + override fun bind(holder: Holder) { + super.bind(holder) + holder.roomNameText.text = title + holder.roomTopicText.setTextOrHide(topic) + holder.memberCountText.text = memberCount + + avatarRenderer.render( + MatrixItem.RoomItem(roomId, title, avatarUrl), + holder.avatarImageView + ) + holder.tabView.tabDepth = depth + } + + override fun unbind(holder: Holder) { + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val avatarImageView by bind(R.id.childRoomAvatar) + val roomNameText by bind(R.id.childRoomName) + val roomTopicText by bind(R.id.childRoomTopic) + val memberCountText by bind(R.id.spaceChildMemberCountText) + val tabView by bind(R.id.spaceChildTabView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt new file mode 100644 index 0000000000..651411b2fe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt @@ -0,0 +1,117 @@ +/* + * 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.spaces.preview + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.core.ui.list.genericItemHeader +import im.vector.app.core.utils.TextUtils +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.internal.session.space.peeking.ISpaceChild +import org.matrix.android.sdk.internal.session.space.peeking.SpaceChildPeekResult +import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult +import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult +import javax.inject.Inject + +class SpacePreviewController @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider +) : TypedEpoxyController() { + + interface InteractionListener + + var interactionListener: InteractionListener? = null + + override fun buildModels(data: SpacePreviewState?) { + val result: SpacePeekResult = data?.peekResult?.invoke() ?: return + + when (result) { + is SpacePeekResult.SpacePeekError -> { + genericFooterItem { + id("failed") + // TODO + text("Failed to resolve") + } + } + is SpacePeekResult.Success -> { + // add summary info + val memberCount = result.summary.roomPeekResult.numJoinedMembers ?: 0 + + spaceTopSummaryItem { + id("info") + formattedMemberCount(stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)) + topic(result.summary.roomPeekResult.topic ?: "") + } + + genericItemHeader { + id("header_rooms") + text(stringProvider.getString(R.string.rooms)) + } + + buildChildren(result.summary.children, 0) + } + } + } + + private fun buildChildren(children: List, depth: Int) { + children.forEach { child -> + when (child) { + is SpaceSubChildPeekResult -> { + when (val roomPeekResult = child.roomPeekResult) { + is PeekResult.Success -> { + subSpaceItem { + id(roomPeekResult.roomId) + roomId(roomPeekResult.roomId) + title(roomPeekResult.name) + depth(depth) + avatarUrl(roomPeekResult.avatarUrl) + avatarRenderer(avatarRenderer) + } + buildChildren(child.children, depth + 1) + } + else -> { + // ?? TODO + } + } + } + is SpaceChildPeekResult -> { + // We have to check if the peek result was success + when (val roomPeekResult = child.roomPeekResult) { + is PeekResult.Success -> { + roomChildItem { + id(child.id) + depth(depth) + roomId(roomPeekResult.roomId) + title(roomPeekResult.name ?: "") + topic(roomPeekResult.topic ?: "") + avatarUrl(roomPeekResult.avatarUrl) + memberCount(TextUtils.formatCountToShortDecimal(roomPeekResult.numJoinedMembers ?: 0)) + avatarRenderer(avatarRenderer) + } + } + else -> { + // What to do here? + } + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt new file mode 100644 index 0000000000..fcf961f23a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -0,0 +1,165 @@ +/* + * 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.spaces.preview + +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import com.jakewharton.rxbinding3.appcompat.navigationClicks +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.VectorBaseFragment +import im.vector.app.databinding.FragmentSpacePreviewBinding +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.spaces.SpacePreviewSharedAction +import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel +import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +@Parcelize +data class SpacePreviewArgs( + val idOrAlias: String +) : Parcelable + +class SpacePreviewFragment @Inject constructor( + private val viewModelFactory: SpacePreviewViewModel.Factory, + private val avatarRenderer: AvatarRenderer, + private val epoxyController: SpacePreviewController +) : VectorBaseFragment(), SpacePreviewViewModel.Factory { + + private val viewModel by fragmentViewModel(SpacePreviewViewModel::class) + lateinit var sharedActionViewModel: SpacePreviewSharedActionViewModel + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSpacePreviewBinding { + return FragmentSpacePreviewBinding.inflate(inflater, container, false) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + sharedActionViewModel = activityViewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) + } + + override fun create(initialState: SpacePreviewState) = viewModelFactory.create(initialState) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewModel.observeViewEvents { + handleViewEvents(it) + } + + views.roomPreviewNoPreviewToolbar.navigationClicks() + .throttleFirst(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) } + .disposeOnDestroyView() + + views.spacePreviewRecyclerView.configureWith(epoxyController) + + views.spacePreviewAcceptInviteButton.debouncedClicks { + viewModel.handle(SpacePreviewViewAction.AcceptInvite) + } + + views.spacePreviewDeclineInviteButton.debouncedClicks { + viewModel.handle(SpacePreviewViewAction.DismissInvite) + } + } + + override fun onDestroyView() { + views.spacePreviewRecyclerView.cleanup() + super.onDestroyView() + } + + override fun invalidate() = withState(viewModel) { + when (it.peekResult) { + is Uninitialized, + is Loading -> { + views.spacePreviewPeekingProgress.isVisible = true + views.spacePreviewButtonBar.isVisible = true + views.spacePreviewAcceptInviteButton.isEnabled = false + views.spacePreviewDeclineInviteButton.isEnabled = false + } + is Fail -> { + views.spacePreviewPeekingProgress.isVisible = false + views.spacePreviewButtonBar.isVisible = false + } + is Success -> { + views.spacePreviewPeekingProgress.isVisible = false + views.spacePreviewButtonBar.isVisible = true + views.spacePreviewAcceptInviteButton.isEnabled = true + views.spacePreviewDeclineInviteButton.isEnabled = true + epoxyController.setData(it) + } + } + updateToolbar(it) + } + + private fun handleViewEvents(viewEvents: SpacePreviewViewEvents) { + when (viewEvents) { + SpacePreviewViewEvents.Dismiss -> { + } + SpacePreviewViewEvents.StartJoining -> { + sharedActionViewModel.post(SpacePreviewSharedAction.ShowModalLoading) + } + SpacePreviewViewEvents.JoinSuccess -> { + sharedActionViewModel.post(SpacePreviewSharedAction.HideModalLoading) + sharedActionViewModel.post(SpacePreviewSharedAction.DismissAction) + } + is SpacePreviewViewEvents.JoinFailure -> { + sharedActionViewModel.post(SpacePreviewSharedAction.HideModalLoading) + sharedActionViewModel.post(SpacePreviewSharedAction.ShowErrorMessage(viewEvents.message ?: getString(R.string.matrix_error))) + } + } + } + + private fun updateToolbar(spacePreviewState: SpacePreviewState) { + when (val preview = spacePreviewState.peekResult.invoke()) { + is SpacePeekResult.Success -> { + val roomPeekResult = preview.summary.roomPeekResult + val mxItem = MatrixItem.RoomItem(roomPeekResult.roomId, roomPeekResult.name, roomPeekResult.avatarUrl) + avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) + views.roomPreviewNoPreviewToolbarTitle.text = roomPeekResult.name + } + is SpacePeekResult.SpacePeekError, + null -> { + // what to do here? + val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spacePreviewState.name, spacePreviewState.avatarUrl) + avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) + views.roomPreviewNoPreviewToolbarTitle.text = spacePreviewState.name + } + } + } + + override fun onStart() { + super.onStart() + viewModel.handle(SpacePreviewViewAction.ViewReady) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt new file mode 100644 index 0000000000..41d94e8c9d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 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.spaces.preview + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult + +data class SpacePreviewState( + val idOrAlias: String, + val name: String? = null, + val avatarUrl: String? = null, + val peekResult: Async = Uninitialized +) : MvRxState { + constructor(args: SpacePreviewArgs) : this(idOrAlias = args.idOrAlias) +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewAction.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewAction.kt new file mode 100644 index 0000000000..6426b89d55 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2021 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.spaces.preview + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class SpacePreviewViewAction : VectorViewModelAction { + object ViewReady : SpacePreviewViewAction() + object AcceptInvite : SpacePreviewViewAction() + object DismissInvite : SpacePreviewViewAction() +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewEvents.kt new file mode 100644 index 0000000000..04645e59ad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewEvents.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 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.spaces.preview + +import im.vector.app.core.platform.VectorViewEvents + +sealed class SpacePreviewViewEvents : VectorViewEvents { + object Dismiss: SpacePreviewViewEvents() + object StartJoining: SpacePreviewViewEvents() + object JoinSuccess: SpacePreviewViewEvents() + data class JoinFailure(val message: String?): SpacePreviewViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt new file mode 100644 index 0000000000..6986db18aa --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -0,0 +1,135 @@ +/* + * 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.spaces.preview + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.ActivityViewModelContext +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.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.space.SpaceService +import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult + +class SpacePreviewViewModel @AssistedInject constructor( + @Assisted private val initialState: SpacePreviewState, + private val session: Session +) : VectorViewModel(initialState) { + + private var initialized = false + + init { + // do we have some things in cache? + session.getRoomSummary(initialState.idOrAlias)?.let { + setState { + copy(name = it.name, avatarUrl = it.avatarUrl) + } + } + } + + @AssistedInject.Factory + interface Factory { + fun create(initialState: SpacePreviewState): SpacePreviewViewModel + } + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: SpacePreviewState): SpacePreviewViewModel? { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + override fun handle(action: SpacePreviewViewAction) { + when (action) { + SpacePreviewViewAction.ViewReady -> handleReady() + SpacePreviewViewAction.AcceptInvite -> handleAcceptInvite() + SpacePreviewViewAction.DismissInvite -> handleDismissInvite() + } + } + + private fun handleDismissInvite() { + TODO("Not yet implemented") + } + + private fun handleAcceptInvite() = withState { state -> + // Here we need to join the space himself as well as the default rooms in that space + val spaceInfo = state.peekResult.invoke() as? SpacePeekResult.Success + + // TODO if we have no summary, we cannot find auto join rooms... + // So maybe we should trigger a retry on summary after the join? + val spaceVia = (spaceInfo?.summary?.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList() + val autoJoinChildren = spaceInfo?.summary?.children + ?.filter { it.default == true } + ?.map { + SpaceService.ChildAutoJoinInfo( + it.id, + // via servers + (it.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList() + ) + } ?: emptyList() + + // trigger modal loading + _viewEvents.post(SpacePreviewViewEvents.StartJoining) + viewModelScope.launch(Dispatchers.IO) { + val joinResult = session.spaceService().joinSpace(spaceInfo?.summary?.idOrAlias ?: initialState.idOrAlias, null, spaceVia, autoJoinChildren) + when (joinResult) { + SpaceService.JoinSpaceResult.Success, + is SpaceService.JoinSpaceResult.PartialSuccess -> { + // For now we don't handle partial success, it's just success + _viewEvents.post(SpacePreviewViewEvents.JoinSuccess) + } + is SpaceService.JoinSpaceResult.Fail -> { + _viewEvents.post(SpacePreviewViewEvents.JoinFailure(joinResult.error?.toString())) + } + } + } + } + + private fun handleReady() { + if (!initialized) { + initialized = true + // peek for the room + setState { + copy(peekResult = Loading()) + } + viewModelScope.launch(Dispatchers.IO) { + try { + val result = session.spaceService().peekSpace(initialState.idOrAlias) + setState { + copy(peekResult = Success(result)) + } + } catch (failure: Throwable) { + setState { + copy(peekResult = Fail(failure)) + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt new file mode 100644 index 0000000000..675e7070e7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTabView.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 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.spaces.preview + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import im.vector.app.R + +class SpaceTabView constructor(context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0) + : LinearLayout(context, attrs, defStyleAttr) { + + constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) {} + constructor(context: Context) : this(context, null, 0) {} + + var tabDepth = 0 + set(value) { + if (field != value) { + field = value + setUpView() + } + } + + init { + setUpView() + } + + private fun setUpView() { + // remove children + removeAllViews() + for (i in 0 until tabDepth) { + inflate(context, R.layout.item_space_tab, this) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTopSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTopSummaryItem.kt new file mode 100644 index 0000000000..c357fb14b3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpaceTopSummaryItem.kt @@ -0,0 +1,46 @@ +/* + * 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.spaces.preview + +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 + +@EpoxyModelClass(layout = R.layout.item_space_top_summary) +abstract class SpaceTopSummaryItem : VectorEpoxyModel() { + + @EpoxyAttribute + var topic: String? = null + + @EpoxyAttribute + lateinit var formattedMemberCount: String + + override fun bind(holder: Holder) { + super.bind(holder) + holder.spaceTopicText.setTextOrHide(topic) + holder.memberCountText.text = formattedMemberCount + } + + class Holder : VectorEpoxyHolder() { + val memberCountText by bind(R.id.spaceSummaryMemberCountText) + val spaceTopicText by bind(R.id.spaceSummaryTopic) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt new file mode 100644 index 0000000000..367a81fe5a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SubSpaceItem.kt @@ -0,0 +1,68 @@ +/* + * 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.spaces.preview + +import android.widget.ImageView +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.features.home.AvatarRenderer +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass(layout = R.layout.item_space_subspace) +abstract class SubSpaceItem : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var roomId: String + + @EpoxyAttribute + lateinit var title: String + + @EpoxyAttribute + var avatarUrl: String? = null + + @EpoxyAttribute + lateinit var avatarRenderer: AvatarRenderer + + @EpoxyAttribute + var depth: Int = 0 + + override fun bind(holder: Holder) { + super.bind(holder) + holder.nameText.text = title + + avatarRenderer.renderSpace( + MatrixItem.RoomItem(roomId, title, avatarUrl), + holder.avatarImageView + ) + holder.tabView.tabDepth = depth + } + + override fun unbind(holder: Holder) { + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val avatarImageView by bind(R.id.childSpaceAvatar) + val nameText by bind(R.id.childSpaceName) + val tabView by bind(R.id.childSpaceTab) + } +} diff --git a/vector/src/main/res/layout/fragment_space_preview.xml b/vector/src/main/res/layout/fragment_space_preview.xml new file mode 100644 index 0000000000..257797548e --- /dev/null +++ b/vector/src/main/res/layout/fragment_space_preview.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_space_roomchild.xml b/vector/src/main/res/layout/item_space_roomchild.xml new file mode 100644 index 0000000000..0fdbd833f4 --- /dev/null +++ b/vector/src/main/res/layout/item_space_roomchild.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_space_subspace.xml b/vector/src/main/res/layout/item_space_subspace.xml new file mode 100644 index 0000000000..ac654dc2b3 --- /dev/null +++ b/vector/src/main/res/layout/item_space_subspace.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_space_tab.xml b/vector/src/main/res/layout/item_space_tab.xml new file mode 100644 index 0000000000..ea08fabea3 --- /dev/null +++ b/vector/src/main/res/layout/item_space_tab.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/vector/src/main/res/layout/item_space_top_summary.xml b/vector/src/main/res/layout/item_space_top_summary.xml new file mode 100644 index 0000000000..e4e2bbdd76 --- /dev/null +++ b/vector/src/main/res/layout/item_space_top_summary.xml @@ -0,0 +1,46 @@ + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index c9cb4729f7..b296c4e5ea 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -508,6 +508,9 @@ Communities No groups + Invites + Spaces + Send logs Send crash logs Send key share requests history From ab4f2429c47a1d239582262f7a401b2cbde07021 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 8 Jan 2021 09:07:27 +0100 Subject: [PATCH 096/230] Use unstable prefixes --- .../android/sdk/api/session/events/model/EventType.kt | 4 ++-- .../matrix/android/sdk/api/session/room/model/RoomType.kt | 4 ++-- .../sdk/api/session/room/model/create/RoomCreateContent.kt | 2 +- .../sdk/internal/session/space/peeking/PeekSpaceTask.kt | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) 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 4be8eea856..99934d3d95 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 @@ -51,8 +51,8 @@ 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" - const val STATE_SPACE_CHILD = "m.space.child" -// const val STATE_SPACE_CHILD = "org.matrix.msc1772.space" +// const val STATE_SPACE_CHILD = "m.space.child" + const val STATE_SPACE_CHILD = "org.matrix.msc1772.space.child" /** * Note that this Event has been deprecated, see diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt index 3958d45d0b..b4932494f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt @@ -18,6 +18,6 @@ package org.matrix.android.sdk.api.session.room.model object RoomType { - const val SPACE = "m.space" - const val MESSAGING = "m.messaging" + const val SPACE = "org.matrix.msc1772.space" // "m.space" +// const val MESSAGING = "org.matrix.msc1840.messaging" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt index 52e5c0e9c7..f9d40d5652 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt @@ -28,5 +28,5 @@ data class RoomCreateContent( @Json(name = "room_version") val roomVersion: String? = null, @Json(name = "predecessor") val predecessor: Predecessor? = null, // Defines the room type, see #RoomType (user extensible) - @Json(name = "type") val type: String? = null + @Json(name = "org.matrix.msc1772.type") val type: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt index 826be0b3aa..a2be75a232 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt @@ -119,15 +119,15 @@ internal class DefaultPeekSpaceTask @Inject constructor( peekChildren(childStateEvents, depth + 1, maxDepth) ) ) - } else if (type == RoomType.MESSAGING || type == null) { + } else + /** if (type == RoomType.MESSAGING || type == null)*/ + { Timber.v("## SPACE_PEEK: room child $entry") spaceChildResults.add( SpaceChildPeekResult( childId, childPeek, entry.second?.default, entry.second?.order ) ) - } else { - // ignore for now? } // let's check child info From e2578a29ed905ffe9095e663d51c325d96f14e9e Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 12 Jan 2021 11:40:09 +0100 Subject: [PATCH 097/230] Basic space join / use tmp msc id / db model update --- .../api/session/room/model/SpaceChildInfo.kt | 25 ++++ .../room/model/create/CreateRoomParams.kt | 3 +- .../sdk/api/session/space/SpaceSummary.kt | 3 +- .../session/space/model/SpaceChildContent.kt | 2 +- .../sdk/internal/database/RealmQueryLatch.kt | 2 + .../database/RealmSessionStoreMigration.kt | 11 +- .../database/mapper/SpaceSummaryMapper.kt | 9 +- .../database/model/SessionRealmModule.kt | 3 +- .../database/model/SpaceChildInfoEntity.kt | 38 ++++++ .../database/model/SpaceSummaryEntity.kt | 2 +- .../sdk/internal/session/room/RoomModule.kt | 5 + .../relationship/RoomRelationshipHelper.kt | 24 +++- .../room/summary/RoomSummaryUpdater.kt | 19 ++- .../session/space/DefaultSpaceService.kt | 35 +++--- .../internal/session/space/JoinSpaceTask.kt | 116 ++++++++++++++++++ .../session/space/peeking/SpacePeekResult.kt | 2 +- .../internal/session/sync/RoomSyncHandler.kt | 1 + vector/src/main/AndroidManifest.xml | 1 + .../im/vector/app/core/di/FragmentModule.kt | 2 +- .../app/features/home/HomeDrawerFragment.kt | 2 +- .../features/spaces/SpaceExploreActivity.kt | 73 +++++++++++ .../SpaceListFragment.kt | 10 +- .../SpaceSummaryController.kt | 10 +- .../{grouplist => spaces}/SpaceSummaryItem.kt | 2 +- .../features/spaces/SpacesListViewModel.kt | 13 +- .../explore/SpaceDirectoryController.kt | 53 ++++++++ .../spaces/explore/SpaceDirectoryFragment.kt | 35 ++++++ .../spaces/explore/SpaceDirectoryViewModel.kt | 101 +++++++++++++++ 28 files changed, 549 insertions(+), 53 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/SpaceExploreActivity.kt rename vector/src/main/java/im/vector/app/features/{grouplist => spaces}/SpaceListFragment.kt (91%) rename vector/src/main/java/im/vector/app/features/{grouplist => spaces}/SpaceSummaryController.kt (95%) rename vector/src/main/java/im/vector/app/features/{grouplist => spaces}/SpaceSummaryItem.kt (98%) create mode 100644 vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt new file mode 100644 index 0000000000..04f3310a77 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model + +data class SpaceChildInfo( + val roomSummary: IRoomSummary?, + val present: Boolean, + val order: String?, + val autoJoin: Boolean, + val viaServers: List +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index 6009649314..880854da58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM // TODO Give a way to include other initial states @@ -112,7 +111,7 @@ open class CreateRoomParams { } } - var roomType: String? = RoomType.MESSAGING + var roomType: String? = null // RoomType.MESSAGING set(value) { field = value if (value != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt index d2be2f18f1..1473ed7a96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.api.session.space import org.matrix.android.sdk.api.session.room.model.IRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo data class SpaceSummary( val spaceId: String, val roomSummary: RoomSummary, - val children: List + val children: List ) : IRoomSummary by roomSummary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt index f7bd067c55..e31ff5af5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -49,5 +49,5 @@ data class SpaceChildContent( * The default flag on a child listing allows a space admin to list the "default" sub-spaces and rooms in that space. * This means that when a user joins the parent space, they will automatically be joined to those default children. */ - @Json(name = "default") val default: Boolean? = true + @Json(name = "default") val default: Boolean? = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt index c9c797304a..04f47bfccd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmQueryLatch.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout +import timber.log.Timber internal suspend fun awaitNotEmptyResult(realmConfiguration: RealmConfiguration, timeoutMillis: Long, @@ -40,6 +41,7 @@ internal suspend fun awaitNotEmptyResult(realmConfiguration: RealmConfigurat val listener = object : RealmChangeListener> { override fun onChange(it: RealmResults) { + Timber.v("## Space: $it") if (it.isNotEmpty()) { result.removeChangeListener(this) latch.complete(Unit) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 2c06a4e8f7..27b2b031e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -29,9 +29,9 @@ import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityField import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields - import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.model.SpaceChildInfoEntityFields import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -207,9 +207,16 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { obj.setString(RoomSummaryEntityFields.ROOM_TYPE, null) } + val spaceChildInfoSchema = realm.schema.create("SpaceChildInfoEntity") + ?.addField(SpaceChildInfoEntityFields.ORDER, String::class.java) + ?.addField(SpaceChildInfoEntityFields.PRESENT, Boolean::class.java) + ?.setNullable(SpaceChildInfoEntityFields.PRESENT, true) + ?.addRealmListField(SpaceChildInfoEntityFields.VIA_SERVERS.`$`, String::class.java) + ?.addRealmObjectField(SpaceChildInfoEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + realm.schema.create("SpaceSummaryEntity") ?.addField(SpaceSummaryEntityFields.SPACE_ID, String::class.java, FieldAttribute.PRIMARY_KEY) ?.addRealmObjectField(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) - ?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, realm.schema.get("RoomSummaryEntity")!!) + ?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, spaceChildInfoSchema!!) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt index 9dee99d7fe..7128501a65 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.database.mapper +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity import javax.inject.Inject @@ -27,7 +28,13 @@ internal class SpaceSummaryMapper @Inject constructor(private val roomSummaryMap spaceId = spaceSummaryEntity.spaceId, roomSummary = roomSummaryMapper.map(spaceSummaryEntity.roomSummaryEntity!!), children = spaceSummaryEntity.children.map { - roomSummaryMapper.map(it) + SpaceChildInfo( + roomSummary = it.roomSummaryEntity?.let { rs -> roomSummaryMapper.map(rs) }, + autoJoin = it.autoJoin ?: false, + present = it.present ?: false, + viaServers = it.viaServers.map { it }, + order = it.order + ) } ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 8c5bb8e990..76116be1a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -62,6 +62,7 @@ import io.realm.annotations.RealmModule UserAccountDataEntity::class, ScalarTokenEntity::class, WellknownIntegrationManagerConfigEntity::class, - SpaceSummaryEntity::class + SpaceSummaryEntity::class, + SpaceChildInfoEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt new file mode 100644 index 0000000000..68667c55fc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 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 org.matrix.android.sdk.internal.database.model + +import io.realm.RealmList +import io.realm.RealmObject + +/** + * Decorates room summary with space related information. + */ +internal open class SpaceChildInfoEntity( + var viaServers: RealmList = RealmList(), + // it's an active child of the space if and only if present is not null and true + var present: Boolean? = null, + // Use for alphabetic ordering of this child + var order: String? = null, + // If true, this child should be join when parent is joined + var autoJoin: Boolean? = null, + // link to the actual room (check type to see if it's a subspace) + var roomSummaryEntity: RoomSummaryEntity? = null +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt index ca54655022..e63b5b9d55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt @@ -22,7 +22,7 @@ import io.realm.annotations.PrimaryKey internal open class SpaceSummaryEntity(@PrimaryKey var spaceId: String = "", var roomSummaryEntity: RoomSummaryEntity? = null, - var children: RealmList = RealmList() + var children: RealmList = RealmList() // TODO public / private .. and more ) : RealmObject() { 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 b7c4246eca..8cc7f41d5b 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 @@ -90,7 +90,9 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask +import org.matrix.android.sdk.internal.session.space.DefaultJoinSpaceTask import org.matrix.android.sdk.internal.session.space.DefaultSpaceService +import org.matrix.android.sdk.internal.session.space.JoinSpaceTask import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import retrofit2.Retrofit @@ -241,6 +243,9 @@ internal abstract class RoomModule { @Binds abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask + @Binds + abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask + @Binds abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt index b1bcfc7077..54b9a17c3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.space.model.SpaceChildContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.query.whereType +import timber.log.Timber /** * Relationship between rooms and spaces @@ -38,13 +39,30 @@ internal class RoomRelationshipHelper(private val realm: Realm, private val roomId: String ) { - fun getDirectChildrenDescriptions(): List { + data class SpaceChildInfo( + val roomId: String, + val present: Boolean, + val order: String?, + val autoJoin: Boolean, + val viaServers: List + ) + + fun getDirectChildrenDescriptions(): List { return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD) .findAll() - .filter { ContentMapper.map(it.root?.content).toModel()?.present == true } +// .filter { ContentMapper.map(it.root?.content).toModel()?.present == true } .mapNotNull { // ContentMapper.map(it.root?.content).toModel() - it.stateKey + ContentMapper.map(it.root?.content).toModel()?.let { scc -> + Timber.d("## Space child desc state event $scc") + SpaceChildInfo( + roomId = it.stateKey, + present = scc.present ?: false, + order = scc.order, + autoJoin = scc.default ?: false, + viaServers = scc.via ?: emptyList() + ) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 7b637cc9e9..11d0ffffde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.summary import io.realm.Realm +import io.realm.kotlin.createObject import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -38,6 +39,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceChildInfoEntity import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates @@ -162,13 +164,26 @@ internal class RoomSummaryUpdater @Inject constructor( } if (roomType == RoomType.SPACE) { - val spaceSummaryEntity = SpaceSummaryEntity.getOrCreate(realm, roomId) + Timber.v("## Space: Updating summary for Space $roomId membership: ${roomSummaryEntity.membership}") + val spaceSummaryEntity = SpaceSummaryEntity() + spaceSummaryEntity.spaceId = roomId spaceSummaryEntity.roomSummaryEntity = roomSummaryEntity spaceSummaryEntity.children.clear() spaceSummaryEntity.children.addAll( RoomRelationshipHelper(realm, roomId).getDirectChildrenDescriptions() - .map { RoomSummaryEntity.getOrCreate(realm, it) } + .map { + Timber.v("## Space: Updating summary for room $roomId with info $it") + realm.createObject().apply { + this.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, it.roomId) + this.order = it.order + this.present = it.present + this.autoJoin = it.autoJoin + }.also { + Timber.v("## Space: Updating summary for room $roomId with children $it") + } + } ) + realm.insertOrUpdate(spaceSummaryEntity) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 4118d74604..973904cbd5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -42,6 +42,7 @@ internal class DefaultSpaceService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val createRoomTask: CreateRoomTask, private val joinRoomTask: JoinRoomTask, + private val joinSpaceTask: JoinSpaceTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, private val roomIdByAliasTask: GetRoomIdByAliasTask, @@ -77,22 +78,24 @@ internal class DefaultSpaceService @Inject constructor( override suspend fun joinSpace(spaceIdOrAlias: String, reason: String?, viaServers: List, autoJoinChild: List): SpaceService.JoinSpaceResult { try { - joinRoomTask.execute(JoinRoomTask.Params(spaceIdOrAlias, reason, viaServers)) - val childJoinFailures = mutableMapOf() - autoJoinChild.forEach { info -> - // TODO what if the child is it self a subspace with some default children? - try { - joinRoomTask.execute(JoinRoomTask.Params(info.roomIdOrAlias, null, info.viaServers)) - } catch (failure: Throwable) { - // TODO, i could already be a member of this room, handle that as it should not be an error in this context - childJoinFailures[info.roomIdOrAlias] = failure - } - } - return if (childJoinFailures.isEmpty()) { - SpaceService.JoinSpaceResult.Success - } else { - SpaceService.JoinSpaceResult.PartialSuccess(childJoinFailures) - } + joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers)) + // TODO partial success + return SpaceService.JoinSpaceResult.Success +// val childJoinFailures = mutableMapOf() +// autoJoinChild.forEach { info -> +// // TODO what if the child is it self a subspace with some default children? +// try { +// joinRoomTask.execute(JoinRoomTask.Params(info.roomIdOrAlias, null, info.viaServers)) +// } catch (failure: Throwable) { +// // TODO, i could already be a member of this room, handle that as it should not be an error in this context +// childJoinFailures[info.roomIdOrAlias] = failure +// } +// } +// return if (childJoinFailures.isEmpty()) { +// SpaceService.JoinSpaceResult.Success +// } else { +// SpaceService.JoinSpaceResult.PartialSuccess(childJoinFailures) +// } } catch (throwable: Throwable) { return SpaceService.JoinSpaceResult.Fail(throwable) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt new file mode 100644 index 0000000000..66a695fc18 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2021 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 org.matrix.android.sdk.internal.session.space + +import io.realm.RealmConfiguration +import kotlinx.coroutines.TimeoutCancellationException +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask +import org.matrix.android.sdk.internal.task.Task +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +internal interface JoinSpaceTask : Task { + data class Params( + val roomIdOrAlias: String, + val reason: String?, + val viaServers: List = emptyList() + ) +} + +internal class DefaultJoinSpaceTask @Inject constructor( + private val roomAPI: RoomAPI, + private val joinRoomTask: JoinRoomTask, + @SessionDatabase + private val realmConfiguration: RealmConfiguration, + private val spaceSummaryDataSource: SpaceSummaryDataSource, + private val eventBus: EventBus +) : JoinSpaceTask { + + override suspend fun execute(params: JoinSpaceTask.Params) { + Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...") + joinRoomTask.execute(JoinRoomTask.Params( + params.roomIdOrAlias, + params.reason, + params.viaServers + )) + Timber.v("## Space: < Joining root space done for ${params.roomIdOrAlias}") + // we want to wait for sync result to check for auto join rooms + + Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...") + try { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm -> + realm.where(SpaceSummaryEntity::class.java) + .apply { + if (params.roomIdOrAlias.startsWith("!")) { + equalTo(SpaceSummaryEntityFields.SPACE_ID, params.roomIdOrAlias) + } else { + equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, params.roomIdOrAlias) + } + } + .equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.MEMBERSHIP_STR, Membership.JOIN.name) + } + } catch (exception: TimeoutCancellationException) { + Timber.w("## Space: > Error created with timeout") + throw CreateRoomFailure.CreatedWithTimeout + } + + Timber.v("## Space: > Sync done ...") + // after that i should have the children (? do i nead to paginate to get state) + val summary = spaceSummaryDataSource.getSpaceSummary(params.roomIdOrAlias) + Timber.v("## Space: Found space summary Name:[${summary?.roomSummary?.name}] children: ${summary?.children?.size}") + summary?.children?.forEach { + val childRoomSummary = it.roomSummary ?: return@forEach + Timber.v("## Space: Processing child :[${childRoomSummary.roomId}] present: ${it.present} autoJoin:${it.autoJoin}") + if (it.present && it.autoJoin) { + // I should try to join as well + if (childRoomSummary.roomType == RoomType.SPACE) { + } else { + try { + Timber.v("## Space: Joining room child ${childRoomSummary.roomId}") + joinRoomTask.execute(JoinRoomTask.Params( + roomIdOrAlias = childRoomSummary.roomId, + reason = "Auto-join parent space", + viaServers = it.viaServers + )) + } catch (failure: Throwable) { + // todo keep track for partial success + Timber.e("## Space: Failed to join room child ${childRoomSummary.roomId}") + } + } + } + } + } +} + +// try { +// awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> +// realm.where(RoomEntity::class.java) +// .equalTo(RoomEntityFields.ROOM_ID, roomId) +// } +// } catch (exception: TimeoutCancellationException) { +// throw CreateRoomFailure.CreatedWithTimeout +// } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt index 63eed2a6c2..a854dd25d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 2bb606e921..6859df1d37 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -212,6 +212,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { + Timber.v("## Space state event: $eventEntity") eventId = event.eventId root = eventEntity } diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 9a1f2e6dfd..e66a123773 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -273,6 +273,7 @@ + () { + + override fun getBinding(): ActivitySimpleBinding = ActivitySimpleBinding.inflate(layoutInflater) + // lateinit var sharedActionViewModel: SpacePreviewSharedActionViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) +// sharedActionViewModel = viewModelProvider.get(SpacePreviewSharedActionViewModel::class.java) +// sharedActionViewModel +// .observe() +// .subscribe { action -> +// when (action) { +// SpacePreviewSharedAction.DismissAction -> finish() +// SpacePreviewSharedAction.ShowModalLoading -> showWaitingView() +// SpacePreviewSharedAction.HideModalLoading -> hideWaitingView() +// is SpacePreviewSharedAction.ShowErrorMessage -> action.error?.let { showSnackbar(it) } +// } +// }.disposeOnDestroy() + + if (isFirstCreation()) { + val simpleName = SpaceDirectoryFragment::class.java.simpleName + val args = intent?.getParcelableExtra(MvRx.KEY_ARG) + if (supportFragmentManager.findFragmentByTag(simpleName) == null) { + supportFragmentManager.commitTransaction { + replace(R.id.simpleFragmentContainer, + SpacePreviewFragment::class.java, + Bundle().apply { this.putParcelable(MvRx.KEY_ARG, args) }, + simpleName + ) + } + } + } + } + + companion object { + fun newIntent(context: Context, spaceId: String): Intent { + return Intent(context, SpaceExploreActivity::class.java).apply { + putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt similarity index 91% rename from vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt rename to vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index 7091c0b86c..e14b920b2c 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -1,21 +1,20 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2021 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 + * 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.grouplist +package im.vector.app.features.spaces import android.os.Bundle import android.view.LayoutInflater @@ -33,9 +32,6 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGroupListBinding import im.vector.app.features.home.HomeActivitySharedAction import im.vector.app.features.home.HomeSharedActionViewModel -import im.vector.app.features.spaces.SpaceListAction -import im.vector.app.features.spaces.SpaceListViewEvents -import im.vector.app.features.spaces.SpacesListViewModel import org.matrix.android.sdk.api.session.space.SpaceSummary import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt rename to vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index dab2cbceae..29d48b4cd1 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -1,29 +1,28 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2021 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 + * 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.grouplist +package im.vector.app.features.spaces import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericItemHeader +import im.vector.app.features.grouplist.homeSpaceSummaryItem import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.spaces.SpaceListViewState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.util.toMatrixItem @@ -87,7 +86,6 @@ class SpaceSummaryController @Inject constructor( summaries .filter { it.roomSummary.membership == Membership.JOIN } .forEach { groupSummary -> - val isSelected = groupSummary.spaceId == selected?.spaceId if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { homeSpaceSummaryItem { diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt similarity index 98% rename from vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt rename to vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index 1a710b764c..525936deab 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -15,7 +15,7 @@ * */ -package im.vector.app.features.grouplist +package im.vector.app.features.spaces import android.widget.ImageView import android.widget.TextView diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index f0d8ae30f7..bdc5c997f6 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -31,14 +31,12 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.resources.StringProvider import im.vector.app.features.grouplist.SelectedSpaceDataSource -import im.vector.app.features.grouplist.SpaceListFragment import io.reactivex.Observable import io.reactivex.functions.BiFunction import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.rx.rx @@ -117,9 +115,11 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp // PRIVATE METHODS ***************************************************************************** private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> - - if (state.selectedSpace?.roomSummary?.membership == Membership.INVITE) { - _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary(state.selectedSpace.roomSummary.roomId)) + // get uptodate version of the space + val summary = session.spaceService().getSpaceSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Equals(action.spaceSummary.spaceId) }) + .firstOrNull() + if (summary?.roomSummary?.membership == Membership.INVITE) { + _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary(summary.roomSummary.roomId)) // viewModelScope.launch(Dispatchers.IO) { // tryOrNull { session.spaceService().peekSpace(action.spaceSummary.spaceId) }.let { // Timber.d("PEEK RESULT/ $it") @@ -139,7 +139,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp val roomSummaryQueryParams = roomSummaryQueryParams() { memberships = listOf(Membership.JOIN, Membership.INVITE) displayName = QueryStringValue.IsNotEmpty - excludeType = listOf(RoomType.MESSAGING, null) + excludeType = listOf(/**RoomType.MESSAGING,$*/ + null) } Observable.combineLatest, List>( session diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt new file mode 100644 index 0000000000..2c46607987 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 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.spaces.explore + +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Incomplete +import com.airbnb.mvrx.Success +import im.vector.app.core.epoxy.errorWithRetryItem +import im.vector.app.core.epoxy.loadingItem + +class SpaceDirectoryController : TypedEpoxyController() { + + override fun buildModels(data: SpaceDirectoryState?) { + when (data?.summary) { + is Success -> { +// val directories = roomDirectoryListCreator.computeDirectories(asyncThirdPartyProtocol()) +// +// directories.forEach { +// buildDirectory(it) +// } + } + is Incomplete -> { + loadingItem { + id("loading") + } + } + is Fail -> { + errorWithRetryItem { + id("error") +// text(errorFormatter.toHumanReadable(asyncThirdPartyProtocol.error)) +// listener { callback?.retry() } + } + } + else -> { + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt new file mode 100644 index 0000000000..6b67e20405 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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.spaces.explore + +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.ViewGroup +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class SpaceDirectoryArgs( + val spaceId: String +) : Parcelable + +class SpaceDirectoryFragment : VectorBaseFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = + FragmentRoomDirectoryPickerBinding.inflate(layoutInflater, container, false) +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt new file mode 100644 index 0000000000..86baae0875 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021 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.spaces.explore + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +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.platform.VectorViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.platform.VectorViewModelAction +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap + +data class SpaceDirectoryState( + // The current filter + val spaceId: String, + val currentFilter: String = "", + val summary: Async = Uninitialized, + // True if more result are available server side + val hasMore: Boolean = false, + // Set of joined roomId / spaces, + val joinedRoomsIds: Set = emptySet() +) : MvRxState { + constructor(args: SpaceDirectoryArgs) : this(spaceId = args.spaceId) +} + +sealed class SpaceDirectoryViewAction : VectorViewModelAction + +sealed class SpaceDirectoryViewEvents : VectorViewEvents + +class SpaceDirectoryViewModel @AssistedInject constructor( + @Assisted initialState: SpaceDirectoryState, + private val session: Session +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: SpaceDirectoryState): SpaceDirectoryViewModel + } + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: SpaceDirectoryState): SpaceDirectoryViewModel? { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + init { + val queryParams = roomSummaryQueryParams { + roomId = QueryStringValue.Equals(initialState.spaceId) + } + + viewModelScope.launch(Dispatchers.IO) { + session + .rx() + .liveSpaceSummaries(queryParams) + .observeOn(Schedulers.computation()) + .map { sum -> Optional.from(sum.firstOrNull()) } + .unwrap() + .execute { async -> + copy(summary = async) + } + } + } + + override fun handle(action: VectorViewModelAction) { + TODO("Not yet implemented") + } +} From 7521a0d3ae48b7faa861694ecd5d9dc62ab5c8b7 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 12 Jan 2021 15:37:39 +0100 Subject: [PATCH 098/230] Fix / post rebase issues --- .../android/sdk/internal/session/space/JoinSpaceTask.kt | 4 +--- .../java/im/vector/app/features/spaces/SpaceSummaryItem.kt | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index 66a695fc18..82dfb55dcc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.space import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException -import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType @@ -46,8 +45,7 @@ internal class DefaultJoinSpaceTask @Inject constructor( private val joinRoomTask: JoinRoomTask, @SessionDatabase private val realmConfiguration: RealmConfiguration, - private val spaceSummaryDataSource: SpaceSummaryDataSource, - private val eventBus: EventBus + private val spaceSummaryDataSource: SpaceSummaryDataSource ) : JoinSpaceTask { override suspend fun execute(params: JoinSpaceTask.Params) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index 525936deab..bf3a47461f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -1,18 +1,17 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2021 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 + * 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.spaces From b7a89f40552d75f4daaf690cd871b31ecc15b710 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 12 Jan 2021 15:37:48 +0100 Subject: [PATCH 099/230] Fix / Syncs breaking on dendrite --- .../matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt | 2 +- .../sdk/internal/session/sync/model/RoomSyncAccountData.kt | 2 +- .../sdk/internal/session/sync/model/RoomSyncEphemeral.kt | 2 +- .../android/sdk/internal/session/sync/model/RoomSyncState.kt | 2 +- .../android/sdk/internal/session/sync/model/RoomSyncTimeline.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 6859df1d37..95fbb2f1b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -456,7 +456,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) { - for (event in accountData.events) { + accountData.events?.forEach { event -> val eventType = event.getClearType() if (eventType == EventType.TAG) { val content = event.getClearContent().toModel() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt index 1c35d812ee..9e0ccde16b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt @@ -25,5 +25,5 @@ internal data class RoomSyncAccountData( /** * List of account data events (array of Event). */ - @Json(name = "events") val events: List = emptyList() + @Json(name = "events") val events: List? = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt index d59dddb3ea..a2e044d947 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt @@ -26,5 +26,5 @@ internal data class RoomSyncEphemeral( /** * List of ephemeral events (array of Event). */ - @Json(name = "events") val events: List = emptyList() + @Json(name = "events") val events: List? = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt index 5355b7eef1..c825cbf31c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt @@ -27,5 +27,5 @@ internal data class RoomSyncState( /** * List of state events (array of Event). The resulting state corresponds to the *start* of the timeline. */ - @Json(name = "events") val events: List = emptyList() + @Json(name = "events") val events: List? = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt index ddf430099a..ce51958f73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt @@ -27,7 +27,7 @@ internal data class RoomSyncTimeline( /** * List of events (array of Event). */ - @Json(name = "events") val events: List = emptyList(), + @Json(name = "events") val events: List? = emptyList(), /** * Boolean which tells whether there are more events on the server From 57f17620b554a01ba5a347728a49106d39443b99 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 13 Jan 2021 14:23:02 +0100 Subject: [PATCH 100/230] Fix Dendrite sync response support --- .../session/room/timeline/TokenChunkEventPersistor.kt | 6 +++--- .../sdk/internal/session/room/uploads/GetUploadsTask.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index c38dcd00a7..903aef16df 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -124,7 +124,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri direction: PaginationDirection): Result { monarchy .awaitTransaction { realm -> - Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") + Timber.v("Start persisting ${receivedChunk.events?.size} events in $roomId towards $direction") val nextToken: String? val prevToken: String? @@ -149,7 +149,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri } ?: ChunkEntity.create(realm, prevToken, nextToken) - if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) { + if (receivedChunk.events.isNullOrEmpty() && !receivedChunk.hasMore()) { handleReachEnd(realm, roomId, direction, currentChunk) } else { handlePagination(realm, roomId, direction, receivedChunk, currentChunk) @@ -189,7 +189,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri receivedChunk: TokenChunkEvent, currentChunk: ChunkEntity ) { - Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") + Timber.v("Add ${receivedChunk.events?.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") val roomMemberContentsByUser = HashMap() val eventList = receivedChunk.events val stateEvents = receivedChunk.stateEvents diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt index 028c3e9193..74b8372b77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt @@ -94,7 +94,7 @@ internal class DefaultGetUploadsTask @Inject constructor( nextToken = chunk.end ?: "", hasMore = chunk.hasMore() ) - events = chunk.events + events = chunk.events ?: emptyList() } var uploadEvents = listOf() From 186024b2717b0c388d88e55eb3fc63b0f76999d9 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 15 Jan 2021 09:57:29 +0100 Subject: [PATCH 101/230] MSC 2946 WIP --- .../sdk/api/session/space/SpaceService.kt | 9 ++ .../sdk/internal/session/SessionComponent.kt | 4 +- .../sdk/internal/session/room/RoomModule.kt | 4 - .../session/space/DefaultSpaceService.kt | 62 ++++++- .../internal/session/space/JoinSpaceTask.kt | 2 +- .../session/space/ResolveSpaceInfoTask.kt | 45 ++++++ .../sdk/internal/session/space/SpaceApi.kt | 42 +++++ .../space/SpaceChildSummaryResponse.kt | 96 +++++++++++ .../sdk/internal/session/space/SpaceModule.kt | 48 ++++++ .../session/space/SpaceSummaryParams.kt | 30 ++++ .../internal/session/space/SpacesResponse.kt | 31 ++++ .../session/space/peeking/PeekSpaceTask.kt | 2 +- .../spaces/preview/SpacePreviewController.kt | 153 ++++++++++-------- .../spaces/preview/SpacePreviewFragment.kt | 35 ++-- .../spaces/preview/SpacePreviewState.kt | 16 +- .../spaces/preview/SpacePreviewViewModel.kt | 138 +++++++++++++--- 16 files changed, 600 insertions(+), 117 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 4043a3f7b4..304fb4d98b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.session.space import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult typealias SpaceSummaryQueryParams = RoomSummaryQueryParams @@ -43,6 +45,11 @@ interface SpaceService { */ suspend fun peekSpace(spaceId: String) : SpacePeekResult + /** + * Get's information of a space by querying the server + */ + suspend fun querySpaceChildren(spaceId: String) : Pair> + /** * Get a live list of space summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[SpaceSummary] @@ -69,4 +76,6 @@ interface SpaceService { reason: String? = null, viaServers: List = emptyList(), autoJoinChild: List) : JoinSpaceResult + + suspend fun rejectInvite(spaceId: String, reason: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 7e1e3d0f70..541c877b1d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker import org.matrix.android.sdk.internal.session.room.send.SendEventWorker import org.matrix.android.sdk.internal.session.search.SearchModule import org.matrix.android.sdk.internal.session.signout.SignOutModule +import org.matrix.android.sdk.internal.session.space.SpaceModule import org.matrix.android.sdk.internal.session.sync.SyncModule import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.session.sync.SyncTokenStore @@ -91,7 +92,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers FederationModule::class, CallModule::class, SearchModule::class, - ThirdPartyModule::class + ThirdPartyModule::class, + SpaceModule::class ] ) @SessionScope 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 8cc7f41d5b..c19c59e6db 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 @@ -90,11 +90,7 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask -import org.matrix.android.sdk.internal.session.space.DefaultJoinSpaceTask import org.matrix.android.sdk.internal.session.space.DefaultSpaceService -import org.matrix.android.sdk.internal.session.space.JoinSpaceTask -import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask -import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import retrofit2.Retrofit @Module diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 973904cbd5..42f4ed4742 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -18,12 +18,17 @@ package org.matrix.android.sdk.internal.session.space import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy +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.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams +import org.matrix.android.sdk.api.session.space.model.SpaceChildContent import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask @@ -31,6 +36,7 @@ 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 import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask +import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult @@ -50,6 +56,8 @@ internal class DefaultSpaceService @Inject constructor( private val roomGetter: RoomGetter, private val spaceSummaryDataSource: SpaceSummaryDataSource, private val peekSpaceTask: PeekSpaceTask, + private val resolveSpaceInfoTask: ResolveSpaceInfoTask, + private val leaveRoomTask: LeaveRoomTask, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, private val taskExecutor: TaskExecutor ) : SpaceService { @@ -76,7 +84,55 @@ internal class DefaultSpaceService @Inject constructor( return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId)) } - override suspend fun joinSpace(spaceIdOrAlias: String, reason: String?, viaServers: List, autoJoinChild: List): SpaceService.JoinSpaceResult { + override suspend fun querySpaceChildren(spaceId: String): Pair> { + return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId)).let { response -> + val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId } + Pair( + first = RoomSummary( + roomId = spaceDesc?.roomId ?: spaceId, + roomType = spaceDesc?.roomType, + name = spaceDesc?.name ?: "", + displayName = spaceDesc?.name ?: "", + topic = spaceDesc?.topic ?: "", + joinedMembersCount = spaceDesc?.numJoinedMembers, + avatarUrl = spaceDesc?.avatarUrl ?: "", + encryptionEventTs = null, + typingUsers = emptyList(), + isEncrypted = false + ), + second = response.rooms + ?.filter { it.roomId != spaceId } + ?.map { childSummary -> + val childStateEv = response.events + ?.firstOrNull { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD } + ?.content.toModel() + SpaceChildInfo( + roomSummary = RoomSummary( + roomId = childSummary.roomId, + roomType = childSummary.roomType, + name = childSummary.name ?: "", + displayName = childSummary.name ?: "", + topic = childSummary.topic ?: "", + joinedMembersCount = childSummary.numJoinedMembers, + avatarUrl = childSummary.avatarUrl ?: "", + encryptionEventTs = null, + typingUsers = emptyList(), + isEncrypted = false + ), + order = childStateEv?.order, + present = childStateEv?.present ?: false, + autoJoin = childStateEv?.default ?: false, + viaServers = childStateEv?.via ?: emptyList() + ) + } ?: emptyList() + ) + } + } + + override suspend fun joinSpace(spaceIdOrAlias: String, + reason: String?, + viaServers: List, + autoJoinChild: List): SpaceService.JoinSpaceResult { try { joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers)) // TODO partial success @@ -100,4 +156,8 @@ internal class DefaultSpaceService @Inject constructor( return SpaceService.JoinSpaceResult.Fail(throwable) } } + + override suspend fun rejectInvite(spaceId: String, reason: String?) { + leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index 82dfb55dcc..1878d2c0f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt new file mode 100644 index 0000000000..1eacdce8df --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt @@ -0,0 +1,45 @@ +/* + * 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.space + +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface ResolveSpaceInfoTask : Task { + data class Params( + val spaceId: String, + val maxRoomPerSpace: Int, + val limit: Int, + val batchToken: String? + ) { + companion object { + fun withId(spaceId: String) = Params(spaceId, 10, 20, null) + } + } +} + +internal class DefaultResolveSpaceInfoTask @Inject constructor( + private val spaceApi: SpaceApi +) : ResolveSpaceInfoTask { + override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse { + val body = SpaceSummaryParams(maxRoomPerSpace = params.maxRoomPerSpace, limit = params.limit, batch = params.batchToken ?: "") + return executeRequest(null) { + apiCall = spaceApi.getSpaces(params.spaceId, body) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt new file mode 100644 index 0000000000..5919a90b99 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -0,0 +1,42 @@ +/* + * 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.space + +import org.matrix.android.sdk.internal.network.NetworkConstants +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.POST +import retrofit2.http.Path + +internal interface SpaceApi { + + /** + * + * POST /_matrix/client/r0/rooms/{roomID}/spaces + * { + * "max_rooms_per_space": 5, // The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1. + * "limit": 100, // The maximum number of rooms/subspaces to return, server can override this, default: 100. + * "batch": "opaque_string" // A token to use if this is a subsequent HTTP hit, default: "". + * } + * + * MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/spaces") + fun getSpaces(@Path("roomId") spaceId: String, + @Body params: SpaceSummaryParams + ): Call +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt new file mode 100644 index 0000000000..5021ff638f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt @@ -0,0 +1,96 @@ +/* + * 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.space + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SpaceChildSummaryResponse( + /** + * The total number of state events which point to or from this room (inbound/outbound edges). + * This includes all m.space.child events in the room, in addition to m.room.parent events which point to this room as a parent. + */ + @Json(name = "num_refs") val numRefs: Int? = null, + + /** + * The room type, which is m.space for subspaces. + * It can be omitted if there is no room type in which case it should be interpreted as a normal room. + */ + @Json(name = "room_type") val roomType: String? = null, + + /** + * Aliases of the room. May be empty. + */ + @Json(name = "aliases") + val aliases: List? = null, + + /** + * The canonical alias of the room, if any. + */ + @Json(name = "canonical_alias") + val canonicalAlias: String? = null, + + /** + * The name of the room, if any. + */ + @Json(name = "name") + val name: String? = null, + + /** + * Required. The number of members joined to the room. + */ + @Json(name = "num_joined_members") + val numJoinedMembers: Int = 0, + + /** + * Required. The ID of the room. + */ + @Json(name = "room_id") + val roomId: String, + + /** + * The topic of the room, if any. + */ + @Json(name = "topic") + val topic: String? = null, + + /** + * Required. Whether the room may be viewed by guest users without joining. + */ + @Json(name = "world_readable") + val worldReadable: Boolean = false, + + /** + * Required. Whether guest users may join the room and participate in it. If they can, + * they will be subject to ordinary power level rules like any other user. + */ + @Json(name = "guest_can_join") + val guestCanJoin: Boolean = false, + + /** + * The URL for the room's avatar, if one is set. + */ + @Json(name = "avatar_url") + val avatarUrl: String? = null, + + /** + * Undocumented item + */ + @Json(name = "m.federate") + val isFederated: Boolean = false +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt new file mode 100644 index 0000000000..ba15f2e981 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 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 org.matrix.android.sdk.internal.session.space + +import dagger.Binds +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask +import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask +import retrofit2.Retrofit + +@Module +internal abstract class SpaceModule { + + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesSpacesAPI(retrofit: Retrofit): SpaceApi { + return retrofit.create(SpaceApi::class.java) + } + } + + @Binds + abstract fun bindResolveSpaceTask(task: DefaultResolveSpaceInfoTask): ResolveSpaceInfoTask + + @Binds + abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask + + @Binds + abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt new file mode 100644 index 0000000000..a3c6b3cc84 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt @@ -0,0 +1,30 @@ +/* + * 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.space + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SpaceSummaryParams( + /** The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1*/ + @Json(name = "max_rooms_per_space") val maxRoomPerSpace: Int = 100, + /** The maximum number of rooms/subspaces to return, server can override this, default: 100 */ + @Json(name = "limit") val limit: Int = 100, + /** A token to use if this is a subsequent HTTP hit, default: "".*/ + @Json(name = "batch") val batch: String = "" +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt new file mode 100644 index 0000000000..20d63c8814 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt @@ -0,0 +1,31 @@ +/* + * 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.space + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Event + +@JsonClass(generateAdapter = true) +internal data class SpacesResponse( + /** Its presence indicates that there are more results to return. */ + @Json(name = "next_batch") val nextBatch: String? = null, + /** Rooms information like name/avatar/type ... */ + @Json(name = "rooms") val rooms: List? = null, + /** These are the edges of the graph. The objects in the array are complete (or stripped?) m.room.parent or m.space.child events. */ + @Json(name = "events") val events: List? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt index a2be75a232..1214befebd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * 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. diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt index 651411b2fe..eee8d1241f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt @@ -17,17 +17,14 @@ package im.vector.app.features.spaces.preview import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success import im.vector.app.R +import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.resources.StringProvider -import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericItemHeader import im.vector.app.core.utils.TextUtils import im.vector.app.features.home.AvatarRenderer -import org.matrix.android.sdk.api.session.room.peeking.PeekResult -import org.matrix.android.sdk.internal.session.space.peeking.ISpaceChild -import org.matrix.android.sdk.internal.session.space.peeking.SpaceChildPeekResult -import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult -import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult import javax.inject.Inject class SpacePreviewController @Inject constructor( @@ -40,78 +37,100 @@ class SpacePreviewController @Inject constructor( var interactionListener: InteractionListener? = null override fun buildModels(data: SpacePreviewState?) { - val result: SpacePeekResult = data?.peekResult?.invoke() ?: return + val result = data?.childInfoList?.invoke() ?: return - when (result) { - is SpacePeekResult.SpacePeekError -> { - genericFooterItem { - id("failed") - // TODO - text("Failed to resolve") - } + val memberCount = data.spaceInfo.invoke()?.memberCount ?: 0 + + spaceTopSummaryItem { + id("info") + formattedMemberCount(stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)) + topic(data.spaceInfo.invoke()?.topic ?: data.topic ?: "") + } + + if (result.isNotEmpty()) { + genericItemHeader { + id("header_rooms") + text(stringProvider.getString(R.string.rooms)) } - is SpacePeekResult.Success -> { - // add summary info - val memberCount = result.summary.roomPeekResult.numJoinedMembers ?: 0 - spaceTopSummaryItem { - id("info") - formattedMemberCount(stringProvider.getQuantityString(R.plurals.room_title_members, memberCount, memberCount)) - topic(result.summary.roomPeekResult.topic ?: "") - } - - genericItemHeader { - id("header_rooms") - text(stringProvider.getString(R.string.rooms)) - } - - buildChildren(result.summary.children, 0) - } + buildChildren(result, 0) } } - private fun buildChildren(children: List, depth: Int) { + private fun buildChildren(children: List, depth: Int) { children.forEach { child -> - when (child) { - is SpaceSubChildPeekResult -> { - when (val roomPeekResult = child.roomPeekResult) { - is PeekResult.Success -> { - subSpaceItem { - id(roomPeekResult.roomId) - roomId(roomPeekResult.roomId) - title(roomPeekResult.name) - depth(depth) - avatarUrl(roomPeekResult.avatarUrl) - avatarRenderer(avatarRenderer) - } - buildChildren(child.children, depth + 1) - } - else -> { - // ?? TODO - } + + if (child.isSubSpace == true) { + subSpaceItem { + id(child.roomId) + roomId(child.roomId) + title(child.name) + depth(depth) + avatarUrl(child.avatarUrl) + avatarRenderer(avatarRenderer) + } + when (child.children) { + is Loading -> { + loadingItem { id("loading_children_${child.roomId}") } + } + is Success -> { + buildChildren(child.children.invoke(), depth + 1) + } + else -> { } } - is SpaceChildPeekResult -> { - // We have to check if the peek result was success - when (val roomPeekResult = child.roomPeekResult) { - is PeekResult.Success -> { - roomChildItem { - id(child.id) - depth(depth) - roomId(roomPeekResult.roomId) - title(roomPeekResult.name ?: "") - topic(roomPeekResult.topic ?: "") - avatarUrl(roomPeekResult.avatarUrl) - memberCount(TextUtils.formatCountToShortDecimal(roomPeekResult.numJoinedMembers ?: 0)) - avatarRenderer(avatarRenderer) - } - } - else -> { - // What to do here? - } - } + } else { + roomChildItem { + id(child.roomId) + depth(depth) + roomId(child.roomId) + title(child.name ?: "") + topic(child.topic ?: "") + avatarUrl(child.avatarUrl) + memberCount(TextUtils.formatCountToShortDecimal(child.memberCount ?: 0)) + avatarRenderer(avatarRenderer) } } +// when (child) { +// is SpaceSubChildPeekResult -> { +// when (val roomPeekResult = child.roomPeekResult) { +// is PeekResult.Success -> { +// subSpaceItem { +// id(roomPeekResult.roomId) +// roomId(roomPeekResult.roomId) +// title(roomPeekResult.name) +// depth(depth) +// avatarUrl(roomPeekResult.avatarUrl) +// avatarRenderer(avatarRenderer) +// } +// buildChildren(child.children, depth + 1) +// } +// else -> { +// // ?? TODO +// } +// } +// } +// is SpaceChildPeekResult -> { +// // We have to check if the peek result was success +// when (val roomPeekResult = child.roomPeekResult) { +// is PeekResult.Success -> { +// roomChildItem { +// id(child.id) +// depth(depth) +// roomId(roomPeekResult.roomId) +// title(roomPeekResult.name ?: "") +// topic(roomPeekResult.topic ?: "") +// avatarUrl(roomPeekResult.avatarUrl) +// memberCount(TextUtils.formatCountToShortDecimal(roomPeekResult.numJoinedMembers ?: 0)) +// avatarRenderer(avatarRenderer) +// } +// } +// else -> { +// // What to do here? +// } +// } +// } +// } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt index fcf961f23a..563b4f39e0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewFragment.kt @@ -40,7 +40,6 @@ import im.vector.app.features.spaces.SpacePreviewSharedActionViewModel import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.util.MatrixItem -import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -99,7 +98,7 @@ class SpacePreviewFragment @Inject constructor( } override fun invalidate() = withState(viewModel) { - when (it.peekResult) { + when (it.spaceInfo) { is Uninitialized, is Loading -> { views.spacePreviewPeekingProgress.isVisible = true @@ -141,21 +140,23 @@ class SpacePreviewFragment @Inject constructor( } private fun updateToolbar(spacePreviewState: SpacePreviewState) { - when (val preview = spacePreviewState.peekResult.invoke()) { - is SpacePeekResult.Success -> { - val roomPeekResult = preview.summary.roomPeekResult - val mxItem = MatrixItem.RoomItem(roomPeekResult.roomId, roomPeekResult.name, roomPeekResult.avatarUrl) - avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) - views.roomPreviewNoPreviewToolbarTitle.text = roomPeekResult.name - } - is SpacePeekResult.SpacePeekError, - null -> { - // what to do here? - val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spacePreviewState.name, spacePreviewState.avatarUrl) - avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) - views.roomPreviewNoPreviewToolbarTitle.text = spacePreviewState.name - } - } +// when (val preview = spacePreviewState.peekResult.invoke()) { +// is SpacePeekResult.Success -> { +// val roomPeekResult = preview.summary.roomPeekResult + val spaceName = spacePreviewState.spaceInfo.invoke()?.name ?: spacePreviewState.name ?: "" + val spaceAvatarUrl = spacePreviewState.spaceInfo.invoke()?.avatarUrl ?: spacePreviewState.avatarUrl + val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spaceName, spaceAvatarUrl) + avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) + views.roomPreviewNoPreviewToolbarTitle.text = spaceName +// } +// is SpacePeekResult.SpacePeekError, +// null -> { +// // what to do here? +// val mxItem = MatrixItem.RoomItem(spacePreviewState.idOrAlias, spacePreviewState.name, spacePreviewState.avatarUrl) +// avatarRenderer.renderSpace(mxItem, views.spacePreviewToolbarAvatar) +// views.roomPreviewNoPreviewToolbarTitle.text = spacePreviewState.name +// } +// } } override fun onStart() { diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt index 41d94e8c9d..cf64672046 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewState.kt @@ -19,13 +19,25 @@ package im.vector.app.features.spaces.preview import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized -import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult data class SpacePreviewState( val idOrAlias: String, val name: String? = null, + val topic: String? = null, val avatarUrl: String? = null, - val peekResult: Async = Uninitialized + val spaceInfo: Async = Uninitialized, + val childInfoList: Async> = Uninitialized ) : MvRxState { constructor(args: SpacePreviewArgs) : this(idOrAlias = args.idOrAlias) } + +data class ChildInfo( + val roomId: String, + val avatarUrl: String?, + val name: String?, + val topic: String?, + val memberCount: Int?, + val isSubSpace: Boolean?, + val viaServers: List?, + val children: Async> +) diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 6986db18aa..d11fe1502b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -23,6 +23,7 @@ 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 @@ -30,9 +31,12 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult +import org.matrix.android.sdk.internal.session.space.peeking.SpaceSubChildPeekResult +import timber.log.Timber class SpacePreviewViewModel @AssistedInject constructor( @Assisted private val initialState: SpacePreviewState, @@ -73,31 +77,29 @@ class SpacePreviewViewModel @AssistedInject constructor( } } - private fun handleDismissInvite() { - TODO("Not yet implemented") + private fun handleDismissInvite() = withState { state -> + // Here we need to join the space himself as well as the default rooms in that space + // TODO modal loading + viewModelScope.launch(Dispatchers.IO) { + try { + session.spaceService().rejectInvite(initialState.idOrAlias, null) + } catch (failure: Throwable) { + Timber.e(failure, "## Space: Failed to reject invite") + } + } } private fun handleAcceptInvite() = withState { state -> // Here we need to join the space himself as well as the default rooms in that space - val spaceInfo = state.peekResult.invoke() as? SpacePeekResult.Success - // TODO if we have no summary, we cannot find auto join rooms... // So maybe we should trigger a retry on summary after the join? - val spaceVia = (spaceInfo?.summary?.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList() - val autoJoinChildren = spaceInfo?.summary?.children - ?.filter { it.default == true } - ?.map { - SpaceService.ChildAutoJoinInfo( - it.id, - // via servers - (it.roomPeekResult as? PeekResult.Success)?.viaServers ?: emptyList() - ) - } ?: emptyList() + val spaceInfo = state.spaceInfo.invoke() + val spaceVia = spaceInfo?.viaServers ?: emptyList() // trigger modal loading _viewEvents.post(SpacePreviewViewEvents.StartJoining) viewModelScope.launch(Dispatchers.IO) { - val joinResult = session.spaceService().joinSpace(spaceInfo?.summary?.idOrAlias ?: initialState.idOrAlias, null, spaceVia, autoJoinChildren) + val joinResult = session.spaceService().joinSpace(initialState.idOrAlias, null, spaceVia, emptyList()) when (joinResult) { SpaceService.JoinSpaceResult.Success, is SpaceService.JoinSpaceResult.PartialSuccess -> { @@ -116,20 +118,110 @@ class SpacePreviewViewModel @AssistedInject constructor( initialized = true // peek for the room setState { - copy(peekResult = Loading()) + copy( + spaceInfo = Loading(), + childInfoList = Loading() + ) } viewModelScope.launch(Dispatchers.IO) { try { - val result = session.spaceService().peekSpace(initialState.idOrAlias) - setState { - copy(peekResult = Success(result)) - } + resolveSpaceInfo() } catch (failure: Throwable) { - setState { - copy(peekResult = Fail(failure)) - } + Timber.e(failure, "## Space: Failed to resolve space info. Fallback to picking") + fallBackResolve() } } } } + + private suspend fun resolveSpaceInfo() { + val resolveResult = session.spaceService().querySpaceChildren(initialState.idOrAlias) + setState { + copy( + spaceInfo = Success( + resolveResult.first.let { + ChildInfo( + roomId = it.roomId, + avatarUrl = it.avatarUrl, + name = it.name, + topic = it.topic, + memberCount = it.joinedMembersCount, + isSubSpace = it.roomType == RoomType.SPACE, + children = Uninitialized, + viaServers = null + ) + } + ), + childInfoList = Success( + resolveResult.second.map { + ChildInfo( + roomId = it.roomSummary?.roomId ?: "", + avatarUrl = it.roomSummary?.avatarUrl, + name = it.roomSummary?.name, + topic = it.roomSummary?.topic, + memberCount = it.roomSummary?.joinedMembersCount, + isSubSpace = it.roomSummary?.roomType == RoomType.SPACE, + children = Uninitialized, + viaServers = null + ) + } + ) + ) + } + } + + private suspend fun fallBackResolve() { + try { + val resolveResult: SpacePeekResult = session.spaceService().peekSpace(initialState.idOrAlias) + val spaceInfo = (resolveResult as? SpacePeekResult.Success)?.summary?.roomPeekResult + setState { + copy( + spaceInfo = Success( + ChildInfo( + roomId = spaceInfo?.roomId ?: initialState.idOrAlias, + avatarUrl = spaceInfo?.avatarUrl, + name = spaceInfo?.name, + topic = spaceInfo?.topic, + memberCount = spaceInfo?.numJoinedMembers, + isSubSpace = true, + children = Uninitialized, + viaServers = spaceInfo?.viaServers + + ) + ), + childInfoList = resolveResult.let { + when (it) { + is SpacePeekResult.Success -> { + (resolveResult as SpacePeekResult.Success).summary.children.mapNotNull { spaceChild -> + val roomPeekResult = spaceChild.roomPeekResult + if (roomPeekResult is PeekResult.Success) { + ChildInfo( + roomId = spaceChild.id, + avatarUrl = roomPeekResult.avatarUrl, + name = roomPeekResult.name, + topic = roomPeekResult.topic, + memberCount = roomPeekResult.numJoinedMembers, + isSubSpace = spaceChild is SpaceSubChildPeekResult, + children = Uninitialized, + viaServers = roomPeekResult.viaServers + + ) + } else { + null + } + } + Success(emptyList()) + } + else -> { + Fail(Exception("Failed to get info")) + } + } + }) + } + } catch (failure: Throwable) { + setState { + copy(spaceInfo = Fail(failure), childInfoList = Fail(failure)) + } + } + } } From a8d7c25244f688965147bfe7f455d9b20e2da2ae Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 10 Feb 2021 16:36:05 +0100 Subject: [PATCH 102/230] rebase fix --- .../java/im/vector/app/features/home/AvatarRenderer.kt | 2 ++ .../java/im/vector/app/features/home/HomeDetailFragment.kt | 1 - .../im/vector/app/features/spaces/SpacesListViewModel.kt | 7 ++++--- .../app/features/spaces/explore/SpaceDirectoryViewModel.kt | 7 ++++--- .../app/features/spaces/preview/SpacePreviewViewModel.kt | 7 ++++--- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 1765372548..f9a68ba465 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -26,7 +26,9 @@ import androidx.core.graphics.drawable.toBitmap import com.amulyakhare.textdrawable.TextDrawable import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.Transformation +import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CircleCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.target.DrawableImageViewTarget import com.bumptech.glide.request.target.Target diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index de24be1a7d..6bc3f27fd4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -25,7 +25,6 @@ import android.view.ViewGroup import android.widget.ImageView import androidx.core.content.ContextCompat import androidx.core.view.isVisible -import androidx.lifecycle.Observer import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index bdc5c997f6..cbfa760f56 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -23,8 +23,9 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel @@ -66,7 +67,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp private val stringProvider: StringProvider ) : VectorViewModel(initialState) { - @AssistedInject.Factory + @AssistedFactory interface Factory { fun create(initialState: SpaceListViewState): SpacesListViewModel } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index 86baae0875..a5d1e16101 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -24,8 +24,9 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction @@ -62,7 +63,7 @@ class SpaceDirectoryViewModel @AssistedInject constructor( private val session: Session ) : VectorViewModel(initialState) { - @AssistedInject.Factory + @AssistedFactory interface Factory { fun create(initialState: SpaceDirectoryState): SpaceDirectoryViewModel } diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index d11fe1502b..f5dd8b76ec 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -25,8 +25,9 @@ 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 dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -54,7 +55,7 @@ class SpacePreviewViewModel @AssistedInject constructor( } } - @AssistedInject.Factory + @AssistedFactory interface Factory { fun create(initialState: SpacePreviewState): SpacePreviewViewModel } From c8916ee83c8b2bdeaafe6dfc94fb85199581c94d Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 11 Feb 2021 13:12:02 +0100 Subject: [PATCH 103/230] Udpate since msc 1772 --- .../sdk/session/space/SpaceCreationTest.kt | 188 ++++++++++++++++++ .../sdk/api/session/events/model/EventType.kt | 7 +- .../api/session/room/alias/RoomAliasError.kt | 2 +- .../room/model/PowerLevelsContentOverride.kt | 67 +++++++ .../api/session/room/model/SpaceChildInfo.kt | 1 - .../room/model/create/CreateRoomParams.kt | 6 +- .../room/powerlevels/PowerLevelsHelper.kt | 5 +- .../api/session/space/CreateSpaceParams.kt | 8 + .../android/sdk/api/session/space/Space.kt | 4 +- .../sdk/api/session/space/SpaceService.kt | 18 +- .../session/space/model/SpaceChildContent.kt | 31 ++- .../session/space/model/SpaceParentContent.kt | 54 +++++ .../database/RealmSessionStoreMigration.kt | 2 - .../database/mapper/SpaceSummaryMapper.kt | 1 - .../database/model/SpaceChildInfoEntity.kt | 4 +- .../alias/RoomAliasAvailabilityChecker.kt | 6 +- .../session/room/create/CreateRoomBody.kt | 4 +- .../relationship/RoomRelationshipHelper.kt | 23 ++- .../room/state/SafePowerLevelContent.kt | 2 +- .../room/summary/RoomSummaryUpdater.kt | 2 +- .../internal/session/space/DefaultSpace.kt | 29 ++- .../session/space/DefaultSpaceService.kt | 40 ++-- .../internal/session/space/JoinSpaceTask.kt | 49 +++-- .../sdk/internal/session/space/SpaceModule.kt | 2 +- .../session/space/peeking/PeekSpaceTask.kt | 10 +- .../im/vector/app/features/command/Command.kt | 4 +- .../app/features/command/CommandParser.kt | 12 ++ .../app/features/command/ParsedCommand.kt | 2 + .../home/room/detail/RoomDetailViewModel.kt | 40 +++- .../createroom/CreateRoomViewModel.kt | 13 +- .../createroom/RoomAliasErrorFormatter.kt | 2 +- .../spaces/preview/SpacePreviewViewModel.kt | 2 +- vector/src/main/res/values/strings.xml | 3 + 33 files changed, 544 insertions(+), 99 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContentOverride.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt new file mode 100644 index 0000000000..90470233a1 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2021 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 org.matrix.android.sdk.session.space + +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.space.SpaceService +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.internal.util.awaitCallback +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class SpaceCreationTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + + @Test + fun createSimplePublicSpace() { + val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true)) + val roomName = "My Space" + val topic = "A public space for test" + val spaceId: String + runBlocking { + spaceId = session.spaceService().createSpace(roomName, topic, null, true) + // wait a bit to let the summry update it self :/ + delay(400) + } + + val syncedSpace = session.spaceService().getSpace(spaceId) + assertEquals(roomName, syncedSpace?.asRoom()?.roomSummary()?.name, "Room name should be set") + assertEquals(topic, syncedSpace?.asRoom()?.roomSummary()?.topic, "Room topic should be set") + // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set") + + assertNotNull(syncedSpace, "Space should be found by Id") + val creationEvent = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE) + val createContent = creationEvent?.content.toModel() + assertEquals(RoomType.SPACE, createContent?.type, "Room type should be space") + + var powerLevelsContent: PowerLevelsContent? = null + commonTestHelper.waitWithLatch { latch -> + commonTestHelper.retryPeriodicallyWithLatch(latch) { + val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel() + powerLevelsContent = toModel + toModel != null + } + } + assertEquals(100, powerLevelsContent?.eventsDefault, "Space-rooms should be created with a power level for events_default of 100") + + commonTestHelper.signOutAndClose(session) + } + + @Test + fun testJoinSimplePublicSpace() { + val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) + val bobSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) + + val roomName = "My Space" + val topic = "A public space for test" + val spaceId: String + runBlocking { + spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true) + // wait a bit to let the summry update it self :/ + delay(400) + } + + // Try to join from bob, it's a public space no need to invite + + val joinResult: SpaceService.JoinSpaceResult + runBlocking { + joinResult = bobSession.spaceService().joinSpace(spaceId) + } + + assertEquals(SpaceService.JoinSpaceResult.Success, joinResult) + + val spaceBobPov = bobSession.spaceService().getSpace(spaceId) + assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set") + assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set") + + commonTestHelper.signOutAndClose(aliceSession) + commonTestHelper.signOutAndClose(bobSession) + } + + @Test + fun testSimplePublicSpaceWithChildren() { + val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) + val bobSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) + + val roomName = "My Space" + val topic = "A public space for test" + val spaceId: String + val firstChild: String + val secondChild: String + + spaceId = runBlocking { aliceSession.spaceService().createSpace(roomName, topic, null, true) } + val syncedSpace = aliceSession.spaceService().getSpace(spaceId) + + // create a room + firstChild = runBlocking { + awaitCallback { + aliceSession.createRoom(CreateRoomParams().apply { + this.name = "FirstRoom" + this.topic = "Description of first room" + this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + }, it) + } + } + + runBlocking { + syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true) + } + + secondChild = runBlocking { + awaitCallback { + aliceSession.createRoom(CreateRoomParams().apply { + this.name = "SecondRoom" + this.topic = "Description of second room" + this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + }, it) + } + } + + runBlocking { + syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false) + } + + // Try to join from bob, it's a public space no need to invite + + val joinResult = runBlocking { + bobSession.spaceService().joinSpace(spaceId) + } + + assertEquals(SpaceService.JoinSpaceResult.Success, joinResult) + + val spaceBobPov = bobSession.spaceService().getSpace(spaceId) + assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set") + assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set") + + // check if bob has joined automatically the first room + + val bobMembershipFirstRoom = bobSession.getRoom(firstChild)?.roomSummary()?.membership + assertEquals(Membership.JOIN, bobMembershipFirstRoom, "Bob should have joined this room") + RoomSummaryQueryParams.Builder() + + val spaceSummaryBobPov = bobSession.spaceService().getSpaceSummaries(roomSummaryQueryParams { + this.roomId = QueryStringValue.Equals(spaceId) + this.memberships = listOf(Membership.JOIN) + }).firstOrNull() + + assertEquals(2, spaceSummaryBobPov?.children?.size ?: -1, "Unexpected number of children") + + commonTestHelper.signOutAndClose(aliceSession) + commonTestHelper.signOutAndClose(bobSession) + } +} 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 99934d3d95..b4a58e5ee6 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 @@ -51,9 +51,13 @@ 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" -// const val STATE_SPACE_CHILD = "m.space.child" + + // const val STATE_SPACE_CHILD = "m.space.child" const val STATE_SPACE_CHILD = "org.matrix.msc1772.space.child" + // const val STATE_SPACE_PARENT = "m.space.parent" + const val STATE_SPACE_PARENT = "org.matrix.msc1772.space.parent" + /** * Note that this Event has been deprecated, see * - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events @@ -76,6 +80,7 @@ object EventType { const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_REJECT = "m.call.reject" const val CALL_HANGUP = "m.call.hangup" + // This type is not processed by the client, just sent to the server const val CALL_REPLACES = "m.call.replaces" 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 index d2cb7c58a9..1102eda11c 100644 --- 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 @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.alias sealed class RoomAliasError : Throwable() { - object AliasEmpty : RoomAliasError() + object AliasIsBlank : RoomAliasError() object AliasNotAvailable : RoomAliasError() object AliasInvalid : RoomAliasError() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContentOverride.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContentOverride.kt new file mode 100644 index 0000000000..577228cf44 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContentOverride.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content. + */ +@JsonClass(generateAdapter = true) +data class PowerLevelsContentOverride( + /** + * The level required to ban a user. Defaults to 50 if unspecified. + */ + @Json(name = "ban") val ban: Int? = null, + /** + * The level required to kick a user. Defaults to 50 if unspecified. + */ + @Json(name = "kick") val kick: Int? = null, + /** + * The level required to invite a user. Defaults to 50 if unspecified. + */ + @Json(name = "invite") val invite: Int? = null, + /** + * The level required to redact an event. Defaults to 50 if unspecified. + */ + @Json(name = "redact") val redact: Int? = null, + /** + * The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified. + */ + @Json(name = "events_default") val eventsDefault: Int? = null, + /** + * The level required to send specific event types. This is a mapping from event type to power level required. + */ + @Json(name = "events") val events: Map? = null, + /** + * The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified. + */ + @Json(name = "users_default") val usersDefault: Int? = null, + /** + * The power levels for specific users. This is a mapping from user_id to power level for that user. + */ + @Json(name = "users") val users: Map? = null, + /** + * The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified. + */ + @Json(name = "state_default") val stateDefault: Int? = null, + /** + * The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key. + */ + @Json(name = "notifications") val notifications: Map? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt index 04f3310a77..38d9f1e74e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session.room.model data class SpaceChildInfo( val roomSummary: IRoomSummary?, - val present: Boolean, val order: String?, val autoJoin: Boolean, val viaServers: List diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index 880854da58..d4b1a8647a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.create import android.net.Uri import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContentOverride import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM @@ -125,7 +125,7 @@ open class CreateRoomParams { /** * The power level content to override in the default power level event */ - var powerLevelContentOverride: PowerLevelsContent? = null + var powerLevelContentOverride: PowerLevelsContentOverride? = null /** * Mark as a direct message room. @@ -149,6 +149,6 @@ open class CreateRoomParams { companion object { private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" - private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + private const val CREATION_CONTENT_KEY_ROOM_TYPE = "org.matrix.msc1772.type" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt index 4f1253c6df..7918376ca9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt @@ -31,9 +31,8 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return the power level */ fun getUserPowerLevelValue(userId: String): Int { - return powerLevelsContent.users.getOrElse(userId) { - powerLevelsContent.usersDefault - } + return powerLevelsContent.users?.get(userId) + ?: powerLevelsContent.usersDefault } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt index 0caa7af14c..9776baf567 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt @@ -16,12 +16,20 @@ package org.matrix.android.sdk.api.session.space +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContentOverride import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams class CreateSpaceParams : CreateRoomParams() { init { + // Space-rooms are distinguished from regular messaging rooms by the m.room.type of m.space roomType = RoomType.SPACE + + // Space-rooms should be created with a power level for events_default of 100, + // to prevent the rooms accidentally/maliciously clogging up with messages from random members of the space. + powerLevelContentOverride = PowerLevelsContentOverride( + eventsDefault = 100 + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 88ac00cc55..616393d00b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -22,7 +22,9 @@ interface Space { fun asRoom() : Room - suspend fun addRoom(roomId: String) + suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean = false) + + suspend fun removeRoom(roomId: String) // fun getChildren() : List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 304fb4d98b..bef3267086 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.api.session.space +import android.net.Uri import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -31,6 +32,11 @@ interface SpaceService { */ suspend fun createSpace(params: CreateSpaceParams): String + /** + * Just a shortcut for space creation for ease of use + */ + suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String + /** * Get a space from a roomId * @param roomId the roomId to look for. @@ -43,12 +49,12 @@ interface SpaceService { * Use this call get preview of children of this space, particularly useful to get a * preview of rooms that you did not join yet. */ - suspend fun peekSpace(spaceId: String) : SpacePeekResult + suspend fun peekSpace(spaceId: String): SpacePeekResult /** * Get's information of a space by querying the server */ - suspend fun querySpaceChildren(spaceId: String) : Pair> + suspend fun querySpaceChildren(spaceId: String): Pair> /** * Get a live list of space summaries. This list is refreshed as soon as the data changes. @@ -64,8 +70,9 @@ interface SpaceService { ) sealed class JoinSpaceResult { - object Success: JoinSpaceResult() - data class Fail(val error: Throwable?): JoinSpaceResult() + object Success : JoinSpaceResult() + data class Fail(val error: Throwable) : JoinSpaceResult() + /** Success fully joined the space, but failed to join all or some of it's rooms */ data class PartialSuccess(val failedRooms: Map) : JoinSpaceResult() @@ -74,8 +81,7 @@ interface SpaceService { suspend fun joinSpace(spaceIdOrAlias: String, reason: String? = null, - viaServers: List = emptyList(), - autoJoinChild: List) : JoinSpaceResult + viaServers: List = emptyList()): JoinSpaceResult suspend fun rejectInvite(spaceId: String, reason: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt index e31ff5af5c..f7abf7e618 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -31,13 +31,9 @@ import com.squareup.moshi.JsonClass data class SpaceChildContent( /** * Key which gives a list of candidate servers that can be used to join the room + * Children where via is not present are ignored. */ @Json(name = "via") val via: List? = null, - /** - * present: true key is included to distinguish from a deleted state event - * Children where present is not present or is not set to true are ignored. - */ - @Json(name = "present") val present: Boolean? = false, /** * The order key is a string which is used to provide a default ordering of siblings in the room list. * (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last. @@ -46,8 +42,25 @@ data class SpaceChildContent( */ @Json(name = "order") val order: String? = null, /** - * The default flag on a child listing allows a space admin to list the "default" sub-spaces and rooms in that space. - * This means that when a user joins the parent space, they will automatically be joined to those default children. + * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should + * be automatically joined by members of that space. + * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.) */ - @Json(name = "default") val default: Boolean? = false -) + @Json(name = "auto_join") val autoJoin: Boolean? = false +) { + /** + * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~), + * or consist of more than 50 characters, are forbidden and should be ignored if received.) + */ + fun validOrder(): String? { + order?.let { + if (order.length > 50) return null + if (!ORDER_VALID_CHAR_REGEX.matches(it)) return null + } + return order + } + + companion object { + private val ORDER_VALID_CHAR_REGEX = "[ -~]+".toRegex() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt new file mode 100644 index 0000000000..b3f7267580 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt @@ -0,0 +1,54 @@ +/* + * 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.space.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Rooms can claim parents via the m.space.parent state event. + * { + * "type": "m.space.parent", + * "state_key": "!space:example.com", + * "content": { + * "via": ["example.com"], + * "present": true, + * "canonical": true, + * } + * } + */ +@JsonClass(generateAdapter = true) +data class SpaceParentContent( + /** + * Key which gives a list of candidate servers that can be used to join the parent. + * Parents where via is not present are ignored. + */ + @Json(name = "via") val via: List? = null, + /** + * present: true key is included to distinguish from a deleted state event + * Parent where present is not present (sic) or is not set to true are ignored. + */ + @Json(name = "present") val present: Boolean? = false, + /** + * Canonical determines whether this is the main parent for the space. + * When a user joins a room with a canonical parent, clients may switch to view the room + * in the context of that space, peeking into it in order to find other rooms and group them together. + * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced: + * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering. + */ + @Json(name = "canonical") val canonical: Boolean? = false +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 27b2b031e4..b14ed9e9c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -209,8 +209,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { val spaceChildInfoSchema = realm.schema.create("SpaceChildInfoEntity") ?.addField(SpaceChildInfoEntityFields.ORDER, String::class.java) - ?.addField(SpaceChildInfoEntityFields.PRESENT, Boolean::class.java) - ?.setNullable(SpaceChildInfoEntityFields.PRESENT, true) ?.addRealmListField(SpaceChildInfoEntityFields.VIA_SERVERS.`$`, String::class.java) ?.addRealmObjectField(SpaceChildInfoEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt index 7128501a65..d08528598d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt @@ -31,7 +31,6 @@ internal class SpaceSummaryMapper @Inject constructor(private val roomSummaryMap SpaceChildInfo( roomSummary = it.roomSummaryEntity?.let { rs -> roomSummaryMapper.map(rs) }, autoJoin = it.autoJoin ?: false, - present = it.present ?: false, viaServers = it.viaServers.map { it }, order = it.order ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt index 68667c55fc..7862207901 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. @@ -24,8 +24,6 @@ import io.realm.RealmObject */ internal open class SpaceChildInfoEntity( var viaServers: RealmList = RealmList(), - // it's an active child of the space if and only if present is not null and true - var present: Boolean? = null, // Use for alphabetic ordering of this child var order: String? = null, // If true, this child should be join when parent is joined 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 index 9faf50dd8b..b39cbaa582 100644 --- 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 @@ -36,7 +36,11 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( @Throws(RoomAliasError::class) suspend fun check(aliasLocalPart: String?) { if (aliasLocalPart.isNullOrEmpty()) { - throw RoomAliasError.AliasEmpty + // don't check empty or not provided alias + return + } + if (aliasLocalPart.isBlank()) { + throw RoomAliasError.AliasIsBlank } // Check alias availability val fullAlias = aliasLocalPart.toFullLocalAlias(userId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index 13d403e2e4..a3f6cd53cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.session.room.create import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContentOverride import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody @@ -111,5 +111,5 @@ internal data class CreateRoomBody( * The power level content to override in the default power level event */ @Json(name = "power_level_content_override") - val powerLevelContentOverride: PowerLevelsContent? + val powerLevelContentOverride: PowerLevelsContentOverride? ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt index 54b9a17c3c..c04f5d3948 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt @@ -41,28 +41,31 @@ internal class RoomRelationshipHelper(private val realm: Realm, data class SpaceChildInfo( val roomId: String, - val present: Boolean, val order: String?, val autoJoin: Boolean, val viaServers: List ) + /** + * Gets the ordered list of valid child description. + */ fun getDirectChildrenDescriptions(): List { return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD) .findAll() -// .filter { ContentMapper.map(it.root?.content).toModel()?.present == true } .mapNotNull { - // ContentMapper.map(it.root?.content).toModel() ContentMapper.map(it.root?.content).toModel()?.let { scc -> Timber.d("## Space child desc state event $scc") - SpaceChildInfo( - roomId = it.stateKey, - present = scc.present ?: false, - order = scc.order, - autoJoin = scc.default ?: false, - viaServers = scc.via ?: emptyList() - ) + // Children where via is not present are ignored. + scc.via?.let { via -> + SpaceChildInfo( + roomId = it.stateKey, + order = scc.validOrder(), + autoJoin = scc.autoJoin ?: false, + viaServers = via + ) + } } } + .sortedBy { it.order } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt index a97709e38b..e5ab87ccac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt @@ -50,7 +50,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict { eventsDefault = content.eventsDefault, events = content.events, usersDefault = content.usersDefault, - users = content.users, + users = content.users ?: emptyMap(), stateDefault = content.stateDefault, notifications = content.notifications.mapValues { content.notificationLevel(it.key) } ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 11d0ffffde..3d01021811 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -99,6 +99,7 @@ internal class RoomSummaryUpdater @Inject constructor( val roomType = ContentMapper.map(roomCreateEvent?.content).toModel()?.type roomSummaryEntity.roomType = roomType + Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]") // Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) @@ -176,7 +177,6 @@ internal class RoomSummaryUpdater @Inject constructor( realm.createObject().apply { this.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, it.roomId) this.order = it.order - this.present = it.present this.autoJoin = it.autoJoin }.also { Timber.v("## Space: Updating summary for room $roomId with children $it") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index ebe845572d..0728c31974 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.internal.session.space +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.model.SpaceChildContent @@ -28,11 +30,34 @@ class DefaultSpace(private val room: Room) : Space { return room } - override suspend fun addRoom(roomId: String) { + override suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean) { asRoom().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, - body = SpaceChildContent(present = true).toContent() + body = SpaceChildContent( + via = viaServers, + autoJoin = autoJoin, + order = order + ).toContent() + ) + } + + override suspend fun removeRoom(roomId: String) { + val existing = asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + .firstOrNull() + ?.content.toModel() + ?: // should we throw here? + return + + // edit state event and set via to null + asRoom().sendStateEvent( + eventType = EventType.STATE_SPACE_CHILD, + stateKey = roomId, + body = SpaceChildContent( + order = existing.order, + via = null, + autoJoin = existing.autoJoin + ).toContent() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 42f4ed4742..906886d5e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.space +import android.net.Uri import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.events.model.EventType @@ -23,6 +24,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.SpaceService @@ -66,6 +68,15 @@ internal class DefaultSpaceService @Inject constructor( return createRoomTask.execute(params) } + override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String { + return createSpace(CreateSpaceParams().apply { + this.name = name + this.topic = topic + this.preset = if (isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + this.avatarUri = avatarUri + }) + } + override fun getSpace(spaceId: String): Space? { return roomGetter.getRoom(spaceId) ?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE } @@ -120,8 +131,7 @@ internal class DefaultSpaceService @Inject constructor( isEncrypted = false ), order = childStateEv?.order, - present = childStateEv?.present ?: false, - autoJoin = childStateEv?.default ?: false, + autoJoin = childStateEv?.autoJoin ?: false, viaServers = childStateEv?.via ?: emptyList() ) } ?: emptyList() @@ -131,30 +141,8 @@ internal class DefaultSpaceService @Inject constructor( override suspend fun joinSpace(spaceIdOrAlias: String, reason: String?, - viaServers: List, - autoJoinChild: List): SpaceService.JoinSpaceResult { - try { - joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers)) - // TODO partial success - return SpaceService.JoinSpaceResult.Success -// val childJoinFailures = mutableMapOf() -// autoJoinChild.forEach { info -> -// // TODO what if the child is it self a subspace with some default children? -// try { -// joinRoomTask.execute(JoinRoomTask.Params(info.roomIdOrAlias, null, info.viaServers)) -// } catch (failure: Throwable) { -// // TODO, i could already be a member of this room, handle that as it should not be an error in this context -// childJoinFailures[info.roomIdOrAlias] = failure -// } -// } -// return if (childJoinFailures.isEmpty()) { -// SpaceService.JoinSpaceResult.Success -// } else { -// SpaceService.JoinSpaceResult.PartialSuccess(childJoinFailures) -// } - } catch (throwable: Throwable) { - return SpaceService.JoinSpaceResult.Fail(throwable) - } + viaServers: List): SpaceService.JoinSpaceResult { + return joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers)) } override suspend fun rejectInvite(spaceId: String, reason: String?) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index 1878d2c0f9..6ee3652761 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.space import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields @@ -32,7 +32,7 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject -internal interface JoinSpaceTask : Task { +internal interface JoinSpaceTask : Task { data class Params( val roomIdOrAlias: String, val reason: String?, @@ -48,13 +48,17 @@ internal class DefaultJoinSpaceTask @Inject constructor( private val spaceSummaryDataSource: SpaceSummaryDataSource ) : JoinSpaceTask { - override suspend fun execute(params: JoinSpaceTask.Params) { + override suspend fun execute(params: JoinSpaceTask.Params): SpaceService.JoinSpaceResult { Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...") - joinRoomTask.execute(JoinRoomTask.Params( - params.roomIdOrAlias, - params.reason, - params.viaServers - )) + try { + joinRoomTask.execute(JoinRoomTask.Params( + params.roomIdOrAlias, + params.reason, + params.viaServers + )) + } catch (failure: Throwable) { + return SpaceService.JoinSpaceResult.Fail(failure) + } Timber.v("## Space: < Joining root space done for ${params.roomIdOrAlias}") // we want to wait for sync result to check for auto join rooms @@ -73,19 +77,32 @@ internal class DefaultJoinSpaceTask @Inject constructor( } } catch (exception: TimeoutCancellationException) { Timber.w("## Space: > Error created with timeout") - throw CreateRoomFailure.CreatedWithTimeout + return SpaceService.JoinSpaceResult.PartialSuccess(emptyMap()) } + val errors = HashMap() Timber.v("## Space: > Sync done ...") - // after that i should have the children (? do i nead to paginate to get state) + // after that i should have the children (? do I need to paginate to get state) val summary = spaceSummaryDataSource.getSpaceSummary(params.roomIdOrAlias) Timber.v("## Space: Found space summary Name:[${summary?.roomSummary?.name}] children: ${summary?.children?.size}") summary?.children?.forEach { val childRoomSummary = it.roomSummary ?: return@forEach - Timber.v("## Space: Processing child :[${childRoomSummary.roomId}] present: ${it.present} autoJoin:${it.autoJoin}") - if (it.present && it.autoJoin) { + Timber.v("## Space: Processing child :[${childRoomSummary.roomId}] autoJoin:${it.autoJoin}") + if (it.autoJoin) { // I should try to join as well if (childRoomSummary.roomType == RoomType.SPACE) { + // recursively join auto-joined child of this space? + when (val subspaceJoinResult = this.execute(JoinSpaceTask.Params(it.roomSummary.roomId, null, it.viaServers))) { + SpaceService.JoinSpaceResult.Success -> { + // nop + } + is SpaceService.JoinSpaceResult.Fail -> { + errors[it.roomSummary.roomId] = subspaceJoinResult.error + } + is SpaceService.JoinSpaceResult.PartialSuccess -> { + errors.putAll(subspaceJoinResult.failedRooms) + } + } } else { try { Timber.v("## Space: Joining room child ${childRoomSummary.roomId}") @@ -95,12 +112,18 @@ internal class DefaultJoinSpaceTask @Inject constructor( viaServers = it.viaServers )) } catch (failure: Throwable) { - // todo keep track for partial success + errors[it.roomSummary.roomId] = failure Timber.e("## Space: Failed to join room child ${childRoomSummary.roomId}") } } } } + + return if (errors.isEmpty()) { + SpaceService.JoinSpaceResult.Success + } else { + SpaceService.JoinSpaceResult.PartialSuccess(errors) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt index ba15f2e981..4612d9e142 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt index 1214befebd..8faed5f784 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt @@ -75,8 +75,8 @@ internal class DefaultPeekSpaceTask @Inject constructor( val childRoomsIds = stateEvents .filter { it.type == EventType.STATE_SPACE_CHILD && !it.stateKey.isNullOrEmpty() - // Children where present is not present or is not set to true are ignored. - && it.content?.toModel()?.present == true + // Children where via is not present are ignored. + && it.content?.toModel()?.via != null } .map { it.stateKey to it.content?.toModel() } @@ -101,7 +101,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( // can't peek :/ spaceChildResults.add( SpaceChildPeekResult( - childId, childPeek, entry.second?.default, entry.second?.order + childId, childPeek, entry.second?.autoJoin, entry.second?.order ) ) // continue to next child @@ -114,7 +114,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( SpaceSubChildPeekResult( childId, childPeek, - entry.second?.default, + entry.second?.autoJoin, entry.second?.order, peekChildren(childStateEvents, depth + 1, maxDepth) ) @@ -125,7 +125,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( Timber.v("## SPACE_PEEK: room child $entry") spaceChildResults.add( SpaceChildPeekResult( - childId, childPeek, entry.second?.default, entry.second?.order + childId, childPeek, entry.second?.autoJoin, entry.second?.order ) ) } diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 4722e824f6..2b78627d88 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -48,7 +48,9 @@ enum class Command(val command: String, val parameters: String, @StringRes val d CONFETTI("/confetti", "", R.string.command_confetti), SNOW("/snow", "", R.string.command_snow), CREATE_SPACE("/createspace", " *", R.string.command_description_create_space), - ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space); + ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space), + JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space), + LEAVE_ROOM("/leave", "", R.string.command_description_leave_room); val length get() = command.length + 1 diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index fe5707ec45..9b190d64fe 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -318,6 +318,18 @@ object CommandParser { rawCommand ) } + Command.JOIN_SPACE.command -> { + val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim() + ParsedCommand.JoinSpace( + spaceIdOrAlias + ) + } + Command.LEAVE_ROOM.command -> { + val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim() + ParsedCommand.LeaveRoom( + spaceIdOrAlias + ) + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index 99b0ae7889..d67caac60a 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -59,4 +59,6 @@ sealed class ParsedCommand { class SendChatEffect(val chatEffect: ChatEffect, val message: String) : ParsedCommand() class CreateSpace(val name: String, val invitees: List) : ParsedCommand() class AddToSpace(val spaceId: String) : ParsedCommand() + class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand() + class LeaveRoom(val roomId: String) : ParsedCommand() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 785449236c..8bd0c60c40 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -831,7 +831,13 @@ class RoomDetailViewModel @AssistedInject constructor( invitedUserIds.addAll(slashCommandResult.invitees) } val spaceId = session.spaceService().createSpace(params) - session.spaceService().getSpace(spaceId)?.addRoom(state.roomId) + session.spaceService().getSpace(spaceId) + ?.addChildren( + state.roomId, + listOf(session.sessionParams.homeServerHost ?: ""), + null, + true + ) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) } @@ -842,7 +848,37 @@ class RoomDetailViewModel @AssistedInject constructor( is ParsedCommand.AddToSpace -> { viewModelScope.launch(Dispatchers.IO) { try { - session.spaceService().getSpace(slashCommandResult.spaceId)?.addRoom(room.roomId) + session.spaceService().getSpace(slashCommandResult.spaceId) + ?.addChildren( + room.roomId, + listOf(session.sessionParams.homeServerHost ?: ""), + null, + false + ) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) + } + } + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) + popDraft() + } + is ParsedCommand.JoinSpace -> { + viewModelScope.launch(Dispatchers.IO) { + try { + session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) + } + } + _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled()) + popDraft() + } + is ParsedCommand.LeaveRoom -> { + viewModelScope.launch(Dispatchers.IO) { + try { + awaitCallback { + session.getRoom(slashCommandResult.roomId)?.leave(null, it) + } } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 33dc6bc054..a86f06b142 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -26,8 +26,8 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown @@ -37,6 +37,8 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session +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.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset @@ -182,6 +184,15 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr return@withState } + if (state.roomVisibilityType is CreateRoomViewState.RoomVisibilityType.Public + && state.roomVisibilityType.aliasLocalPart.isBlank()) { + // we require an alias for public rooms + setState { + copy(asyncCreateRoomRequest = Fail(CreateRoomFailure.AliasError(RoomAliasError.AliasIsBlank))) + } + return@withState + } + setState { copy(asyncCreateRoomRequest = Loading()) } 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 index 7a23a79ab3..43cd460445 100644 --- 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 @@ -26,7 +26,7 @@ class RoomAliasErrorFormatter @Inject constructor( ) { fun format(roomAliasError: RoomAliasError?): String? { return when (roomAliasError) { - is RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty + is RoomAliasError.AliasIsBlank -> 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 diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index f5dd8b76ec..37dde7d287 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -100,7 +100,7 @@ class SpacePreviewViewModel @AssistedInject constructor( // trigger modal loading _viewEvents.post(SpacePreviewViewEvents.StartJoining) viewModelScope.launch(Dispatchers.IO) { - val joinResult = session.spaceService().joinSpace(initialState.idOrAlias, null, spaceVia, emptyList()) + val joinResult = session.spaceService().joinSpace(initialState.idOrAlias, null, spaceVia) when (joinResult) { SpaceService.JoinSpaceResult.Success, is SpaceService.JoinSpaceResult.PartialSuccess -> { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b296c4e5ea..37e6c3bbcc 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3248,6 +3248,9 @@ Event content Create a community + Create a Spcae + Join the Space with the given id + Leave room with given id (or current room if null) Sending Sent From 2cc5c76fb38a3a1e50d18c9096bed981952f4334 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Feb 2021 10:02:33 +0100 Subject: [PATCH 104/230] hide dev commands from completion --- .../command/AutocompleteCommandPresenter.kt | 23 +++++--- .../im/vector/app/features/command/Command.kt | 54 +++++++++---------- vector/src/main/res/values/strings.xml | 2 +- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt index d121c68557..aabf15aebe 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt @@ -21,10 +21,12 @@ import androidx.recyclerview.widget.RecyclerView import im.vector.app.features.autocomplete.AutocompleteClickListener import im.vector.app.features.autocomplete.RecyclerViewPresenter import im.vector.app.features.command.Command +import im.vector.app.features.settings.VectorPreferences import javax.inject.Inject class AutocompleteCommandPresenter @Inject constructor(context: Context, - private val controller: AutocompleteCommandController) : + private val controller: AutocompleteCommandController, + private val vectorPreferences: VectorPreferences) : RecyclerViewPresenter(context), AutocompleteClickListener { init { @@ -40,13 +42,18 @@ class AutocompleteCommandPresenter @Inject constructor(context: Context, } override fun onQuery(query: CharSequence?) { - val data = Command.values().filter { - if (query.isNullOrEmpty()) { - true - } else { - it.command.startsWith(query, 1, true) - } - } + val data = Command.values() + .filter { + if (it.isDevCommand && !vectorPreferences.developerMode()) { + return@filter false + } + + if (query.isNullOrEmpty()) { + true + } else { + it.command.startsWith(query, 1, true) + } + } controller.setData(data) } diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 2b78627d88..0b210cf298 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -24,33 +24,33 @@ import im.vector.app.R * the user can write theses messages to perform some actions * the list will be displayed in this order */ -enum class Command(val command: String, val parameters: String, @StringRes val description: Int) { - EMOTE("/me", "", R.string.command_description_emote), - BAN_USER("/ban", " [reason]", R.string.command_description_ban_user), - UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user), - SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user), - RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user), - INVITE("/invite", " [reason]", R.string.command_description_invite_user), - JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room), - PART("/part", " [reason]", R.string.command_description_part_room), - TOPIC("/topic", "", R.string.command_description_topic), - KICK_USER("/kick", " [reason]", R.string.command_description_kick_user), - CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick), - MARKDOWN("/markdown", "", R.string.command_description_markdown), - RAINBOW("/rainbow", "", R.string.command_description_rainbow), - RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote), - CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token), - SPOILER("/spoiler", "", R.string.command_description_spoiler), - POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll), - SHRUG("/shrug", "", R.string.command_description_shrug), - PLAIN("/plain", "", R.string.command_description_plain), - DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session), - CONFETTI("/confetti", "", R.string.command_confetti), - SNOW("/snow", "", R.string.command_snow), - CREATE_SPACE("/createspace", " *", R.string.command_description_create_space), - ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space), - JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space), - LEAVE_ROOM("/leave", "", R.string.command_description_leave_room); +enum class Command(val command: String, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean) { + EMOTE("/me", "", R.string.command_description_emote, false), + BAN_USER("/ban", " [reason]", R.string.command_description_ban_user, false), + UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user, false), + SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user, false), + RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user, false), + INVITE("/invite", " [reason]", R.string.command_description_invite_user, false), + JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), + PART("/part", " [reason]", R.string.command_description_part_room, false), + TOPIC("/topic", "", R.string.command_description_topic, false), + KICK_USER("/kick", " [reason]", R.string.command_description_kick_user, false), + CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false), + MARKDOWN("/markdown", "", R.string.command_description_markdown, false), + RAINBOW("/rainbow", "", R.string.command_description_rainbow, false), + RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote, false), + CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false), + SPOILER("/spoiler", "", R.string.command_description_spoiler, false), + POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll, false), + SHRUG("/shrug", "", R.string.command_description_shrug, false), + PLAIN("/plain", "", R.string.command_description_plain, false), + DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false), + CONFETTI("/confetti", "", R.string.command_confetti, false), + SNOW("/snow", "", R.string.command_snow, false), + CREATE_SPACE("/createspace", " *", R.string.command_description_create_space, true), + ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true), + JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true), + LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true); val length get() = command.length + 1 diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 37e6c3bbcc..84b2262510 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3248,7 +3248,7 @@ Event content Create a community - Create a Spcae + Create a Space Join the Space with the given id Leave room with given id (or current room if null) From 5aa698768a9304a5de4e636d7d3df3ba348aee81 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Feb 2021 10:17:11 +0100 Subject: [PATCH 105/230] Support update of order/autojoin of child --- .../android/sdk/api/session/space/Space.kt | 6 +++ .../sdk/api/session/space/SpaceService.kt | 5 --- .../internal/session/space/DefaultSpace.kt | 43 ++++++++++++++++--- vector/src/main/res/values/strings.xml | 1 - 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 616393d00b..01a0dbc929 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -26,5 +26,11 @@ interface Space { suspend fun removeRoom(roomId: String) + @Throws + suspend fun setChildrenOrder(roomId: String, order: String?) + + @Throws + suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) + // fun getChildren() : List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index bef3267086..3e30f14748 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -64,11 +64,6 @@ interface SpaceService { fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List - data class ChildAutoJoinInfo( - val roomIdOrAlias: String, - val viaServers: List = emptyList() - ) - sealed class JoinSpaceResult { object Success : JoinSpaceResult() data class Fail(val error: Throwable) : JoinSpaceResult() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 0728c31974..264cfd44ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.model.SpaceChildContent +import java.lang.IllegalArgumentException class DefaultSpace(private val room: Room) : Space { @@ -61,11 +62,39 @@ class DefaultSpace(private val room: Room) : Space { ) } -// override fun getChildren(): List { -// // asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD)).mapNotNull { -// // // statekeys are the roomIds -// // -// // } -// return emptyList() -// } + override suspend fun setChildrenOrder(roomId: String, order: String?) { + val existing = asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + .firstOrNull() + ?.content.toModel() + ?: throw IllegalArgumentException("$roomId is not a child of this space") + + // edit state event and set via to null + asRoom().sendStateEvent( + eventType = EventType.STATE_SPACE_CHILD, + stateKey = roomId, + body = SpaceChildContent( + order = order, + via = existing.via, + autoJoin = existing.autoJoin + ).toContent() + ) + } + + override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) { + val existing = asRoom().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + .firstOrNull() + ?.content.toModel() + ?: throw IllegalArgumentException("$roomId is not a child of this space") + + // edit state event and set via to null + asRoom().sendStateEvent( + eventType = EventType.STATE_SPACE_CHILD, + stateKey = roomId, + body = SpaceChildContent( + order = existing.order, + via = existing.via, + autoJoin = autoJoin + ).toContent() + ) + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 84b2262510..01adc31bb1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3247,7 +3247,6 @@ State event sent! Event content - Create a community Create a Space Join the Space with the given id Leave room with given id (or current room if null) From 883f70306f48d32f901795190a657742da03bbd3 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 23 Feb 2021 09:56:46 +0100 Subject: [PATCH 106/230] Rebase Fixes --- .../org/matrix/android/sdk/session/space/SpaceCreationTest.kt | 2 +- .../sdk/api/session/room/powerlevels/PowerLevelsHelper.kt | 2 +- .../sdk/internal/session/room/state/SafePowerLevelContent.kt | 2 +- .../session/room/timeline/TokenChunkEventPersistor.kt | 4 ++-- .../sdk/internal/session/room/uploads/GetUploadsTask.kt | 2 +- .../app/features/spaces/explore/SpaceDirectoryFragment.kt | 2 +- .../app/features/spaces/preview/SpacePreviewViewModel.kt | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 90470233a1..2a99594321 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt index 7918376ca9..eb8dabb48e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt @@ -31,7 +31,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) { * @return the power level */ fun getUserPowerLevelValue(userId: String): Int { - return powerLevelsContent.users?.get(userId) + return powerLevelsContent.users.get(userId) ?: powerLevelsContent.usersDefault } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt index e5ab87ccac..a97709e38b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt @@ -50,7 +50,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict { eventsDefault = content.eventsDefault, events = content.events, usersDefault = content.usersDefault, - users = content.users ?: emptyMap(), + users = content.users, stateDefault = content.stateDefault, notifications = content.notifications.mapValues { content.notificationLevel(it.key) } ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 903aef16df..a7cba2fe99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -124,7 +124,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri direction: PaginationDirection): Result { monarchy .awaitTransaction { realm -> - Timber.v("Start persisting ${receivedChunk.events?.size} events in $roomId towards $direction") + Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") val nextToken: String? val prevToken: String? @@ -189,7 +189,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri receivedChunk: TokenChunkEvent, currentChunk: ChunkEntity ) { - Timber.v("Add ${receivedChunk.events?.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") + Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") val roomMemberContentsByUser = HashMap() val eventList = receivedChunk.events val stateEvents = receivedChunk.stateEvents diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt index 74b8372b77..028c3e9193 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt @@ -94,7 +94,7 @@ internal class DefaultGetUploadsTask @Inject constructor( nextToken = chunk.end ?: "", hasMore = chunk.hasMore() ) - events = chunk.events ?: emptyList() + events = chunk.events } var uploadEvents = listOf() diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt index 6b67e20405..d776d9ff51 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryFragment.kt @@ -21,7 +21,7 @@ import android.view.LayoutInflater import android.view.ViewGroup import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class SpaceDirectoryArgs( diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 37dde7d287..987884d8c9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -78,7 +78,7 @@ class SpacePreviewViewModel @AssistedInject constructor( } } - private fun handleDismissInvite() = withState { state -> + private fun handleDismissInvite() { // Here we need to join the space himself as well as the default rooms in that space // TODO modal loading viewModelScope.launch(Dispatchers.IO) { @@ -108,7 +108,7 @@ class SpacePreviewViewModel @AssistedInject constructor( _viewEvents.post(SpacePreviewViewEvents.JoinSuccess) } is SpaceService.JoinSpaceResult.Fail -> { - _viewEvents.post(SpacePreviewViewEvents.JoinFailure(joinResult.error?.toString())) + _viewEvents.post(SpacePreviewViewEvents.JoinFailure(joinResult.error.toString())) } } } From d8a32298196bf1c4d28dab87bbd587b040a6e54f Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 23 Feb 2021 11:52:29 +0100 Subject: [PATCH 107/230] a11y fixes --- .../res/layout/fragment_space_preview.xml | 37 ++++++++++--------- vector/src/main/res/layout/item_space.xml | 2 + .../main/res/layout/item_space_roomchild.xml | 6 ++- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/vector/src/main/res/layout/fragment_space_preview.xml b/vector/src/main/res/layout/fragment_space_preview.xml index 257797548e..5781ddb570 100644 --- a/vector/src/main/res/layout/fragment_space_preview.xml +++ b/vector/src/main/res/layout/fragment_space_preview.xml @@ -16,25 +16,25 @@ style="@style/VectorToolbarStyle" android:layout_width="match_parent" android:layout_height="?actionBarSize" - app:navigationIcon="@drawable/ic_x_18dp" - android:elevation="4dp"> + android:elevation="4dp" + app:navigationIcon="@drawable/ic_x_18dp"> - - - - - - - - - - - - + + + + + + + + + + + + + android:background="?list_divider_color" /> + android:gravity="center_horizontal" + android:orientation="horizontal"> + android:layout_height="match_parent" + android:layout_marginStart="8dp" /> Date: Mon, 22 Feb 2021 19:59:19 +0100 Subject: [PATCH 108/230] Creation wizard WIP --- vector/src/main/AndroidManifest.xml | 1 + .../im/vector/app/core/di/FragmentModule.kt | 18 ++ .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../form/FormEditableSquareAvatarItem.kt | 80 +++++++++ .../vector/app/features/home/HomeActivity.kt | 4 + .../features/home/HomeActivitySharedAction.kt | 1 + .../features/spaces/SpaceCreationActivity.kt | 135 ++++++++++++++ .../app/features/spaces/SpaceListFragment.kt | 5 + .../features/spaces/SpaceSummaryController.kt | 12 ++ .../features/spaces/SpacesListViewModel.kt | 7 + .../spaces/create/ChooseSpaceTypeFragment.kt | 49 +++++ .../create/CreateSpaceDefaultRoomsFragment.kt | 56 ++++++ .../create/CreateSpaceDetailsFragment.kt | 70 ++++++++ .../spaces/create/CreateSpaceViewModel.kt | 170 ++++++++++++++++++ .../create/SpaceDefaultRoomEpoxyController.kt | 93 ++++++++++ .../create/SpaceDetailEpoxyController.kt | 90 ++++++++++ .../spaces/create/WizardButtonView.kt | 98 ++++++++++ .../src/main/res/drawable/ic_camera_plain.xml | 10 ++ .../src/main/res/drawable/ic_public_room.xml | 13 ++ .../src/main/res/drawable/ic_room_private.xml | 10 ++ .../fragment_space_create_choose_type.xml | 71 ++++++++ ...agment_space_create_generic_epoxy_form.xml | 35 ++++ .../layout/item_editable_square_avatar.xml | 67 +++++++ .../res/layout/view_space_type_button.xml | 68 +++++++ vector/src/main/res/values/attrs.xml | 12 +- vector/src/main/res/values/strings.xml | 18 ++ 26 files changed, 1194 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt create mode 100644 vector/src/main/res/drawable/ic_camera_plain.xml create mode 100644 vector/src/main/res/drawable/ic_public_room.xml create mode 100644 vector/src/main/res/drawable/ic_room_private.xml create mode 100644 vector/src/main/res/layout/fragment_space_create_choose_type.xml create mode 100644 vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml create mode 100644 vector/src/main/res/layout/item_editable_square_avatar.xml create mode 100644 vector/src/main/res/layout/view_space_type_button.xml diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index e66a123773..205669fb8a 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -274,6 +274,7 @@ + () { + + @EpoxyAttribute + var avatarRenderer: AvatarRenderer? = null + + @EpoxyAttribute + var matrixItem: MatrixItem? = null + + @EpoxyAttribute + var enabled: Boolean = true + + @EpoxyAttribute + var imageUri: Uri? = null + + @EpoxyAttribute + var clickListener: ClickListener? = null + + @EpoxyAttribute + var deleteListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.imageContainer.onClick(clickListener?.takeIf { enabled }) + if (matrixItem != null) { + avatarRenderer?.renderSpace(matrixItem!!, holder.image) + } else { + GlideApp.with(holder.image) + .load(imageUri) + .apply(RequestOptions.circleCropTransform()) + .into(holder.image) + } + holder.delete.isVisible = enabled && (imageUri != null || matrixItem?.avatarUrl?.isNotEmpty() == true) + holder.delete.onClick(deleteListener?.takeIf { enabled }) + } + + override fun unbind(holder: Holder) { + avatarRenderer?.clear(holder.image) + GlideApp.with(holder.image).clear(holder.image) + super.unbind(holder) + } + class Holder : VectorEpoxyHolder() { + val imageContainer by bind(R.id.itemEditableAvatarImageContainer) + val image by bind(R.id.itemEditableAvatarImage) + val delete by bind(R.id.itemEditableAvatarDelete) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 138ffc26f4..982b59263d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -54,6 +54,7 @@ import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity +import im.vector.app.features.spaces.SpaceCreationActivity import im.vector.app.features.spaces.SpacePreviewActivity import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel @@ -145,6 +146,9 @@ class HomeActivity : is HomeActivitySharedAction.OpenSpacePreview -> { startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId)) } + is HomeActivitySharedAction.AddSpace -> { + startActivity(SpaceCreationActivity.newIntent(this)) + } }.exhaustive } .disposeOnDestroy() diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt index f72354465b..72e3523336 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt @@ -25,5 +25,6 @@ sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction() object OpenGroup : HomeActivitySharedAction() + object AddSpace : HomeActivitySharedAction() data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt new file mode 100644 index 0000000000..65c8102020 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021 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.spaces + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.MenuItem +import androidx.fragment.app.Fragment +import com.airbnb.mvrx.viewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.toMvRxBundle +import im.vector.app.core.platform.SimpleFragmentActivity +import im.vector.app.features.spaces.create.ChooseSpaceTypeFragment +import im.vector.app.features.spaces.create.CreateSpaceAction +import im.vector.app.features.spaces.create.CreateSpaceDefaultRoomsFragment +import im.vector.app.features.spaces.create.CreateSpaceDetailsFragment +import im.vector.app.features.spaces.create.CreateSpaceEvents +import im.vector.app.features.spaces.create.CreateSpaceState +import im.vector.app.features.spaces.create.CreateSpaceViewModel +import javax.inject.Inject + +class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Factory { + + @Inject lateinit var viewModelFactory: CreateSpaceViewModel.Factory + + override fun injectWith(injector: ScreenComponent) { + super.injectWith(injector) + injector.inject(this) + } + + val viewModel: CreateSpaceViewModel by viewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (isFirstCreation()) { + when (withState(viewModel) { it.step }) { + CreateSpaceState.Step.ChooseType -> { + navigateToFragment(ChooseSpaceTypeFragment::class.java) + } + CreateSpaceState.Step.SetDetails -> { + navigateToFragment(ChooseSpaceTypeFragment::class.java) + } + CreateSpaceState.Step.AddRooms -> TODO() + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + onBackPressed() + return true + } + return super.onOptionsItemSelected(item) + } + + override fun initUiAndData() { + super.initUiAndData() + viewModel.subscribe(this) { + renderState(it) + } + + viewModel.observeViewEvents { + when (it) { + CreateSpaceEvents.NavigateToDetails -> { + navigateToFragment(CreateSpaceDetailsFragment::class.java) + } + CreateSpaceEvents.NavigateToChooseType -> { + navigateToFragment(ChooseSpaceTypeFragment::class.java) + } + CreateSpaceEvents.Dismiss -> { + finish() + } + CreateSpaceEvents.NavigateToAddRooms -> { + navigateToFragment(CreateSpaceDefaultRoomsFragment::class.java) + } + } + } + } + + private fun navigateToFragment(fragmentClass: Class) { + val frag = supportFragmentManager.findFragmentByTag(fragmentClass.name) ?: createFragment(fragmentClass, Bundle().toMvRxBundle()) + supportFragmentManager.beginTransaction() + .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) + .replace(R.id.container, + frag, + fragmentClass.name + ) + .commit() + } + + override fun onBackPressed() { + viewModel.handle(CreateSpaceAction.OnBackPressed) + } + + private fun renderState(state: CreateSpaceState) { + val titleRes = when (state.step) { + CreateSpaceState.Step.ChooseType -> R.string.activity_create_space_title + CreateSpaceState.Step.SetDetails -> R.string.your_public_space + CreateSpaceState.Step.AddRooms -> R.string.your_public_space + } + supportActionBar?.let { + it.title = getString(titleRes) + } ?: run { + setTitle(getString(titleRes)) + } + } + + companion object { + fun newIntent(context: Context): Intent { + return Intent(context, SpaceCreationActivity::class.java).apply { + // putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId)) + } + } + } + + override fun create(initialState: CreateSpaceState): CreateSpaceViewModel = viewModelFactory.create(initialState) +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index e14b920b2c..bc1af38473 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -57,6 +57,7 @@ class SpaceListFragment @Inject constructor( when (it) { is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id)) is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) + is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace) }.exhaustive } } @@ -78,4 +79,8 @@ class SpaceListFragment @Inject constructor( override fun onSpaceSelected(spaceSummary: SpaceSummary) { viewModel.handle(SpaceListAction.SelectSpace(spaceSummary)) } + + override fun onAddSpaceSelected() { + viewModel.handle(SpaceListAction.AddSpace) + } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index 29d48b4cd1..d1bc3c6e1c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -19,8 +19,10 @@ package im.vector.app.features.spaces import com.airbnb.epoxy.EpoxyController import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericButtonItem import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericItemHeader +import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.grouplist.homeSpaceSummaryItem import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.session.room.model.Membership @@ -103,9 +105,19 @@ class SpaceSummaryController @Inject constructor( } } } + + // Temporary item to create a new Space (will move with final design) + + genericButtonItem { + id("create") + text(stringProvider.getString(R.string.add_space)) + iconRes(R.drawable.ic_add_black) + buttonClickAction(DebouncedClickListener({ callback?.onAddSpaceSelected() })) + } } interface Callback { fun onSpaceSelected(spaceSummary: SpaceSummary) + fun onAddSpaceSelected() } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index cbfa760f56..b8089d6f98 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -46,6 +46,7 @@ const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" sealed class SpaceListAction : VectorViewModelAction { data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction() + object AddSpace : SpaceListAction() } /** @@ -54,6 +55,7 @@ sealed class SpaceListAction : VectorViewModelAction { sealed class SpaceListViewEvents : VectorViewEvents { object OpenSpace : SpaceListViewEvents() data class OpenSpaceSummary(val id: String) : SpaceListViewEvents() + object AddSpace : SpaceListViewEvents() } data class SpaceListViewState( @@ -110,6 +112,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp override fun handle(action: SpaceListAction) { when (action) { is SpaceListAction.SelectSpace -> handleSelectSpace(action) + else -> handleAddSpace() } } @@ -136,6 +139,10 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } } + private fun handleAddSpace() { + _viewEvents.post(SpaceListViewEvents.AddSpace) + } + private fun observeGroupSummaries() { val roomSummaryQueryParams = roomSummaryQueryParams() { memberships = listOf(Membership.JOIN, Membership.INVITE) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt new file mode 100644 index 0000000000..080bb99ea4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.DebouncedClickListener +import im.vector.app.databinding.FragmentSpaceCreateChooseTypeBinding +import javax.inject.Inject + +class ChooseSpaceTypeFragment @Inject constructor( + // private val viewModelFactory: CreateSpaceViewModel.Factory, +) : VectorBaseFragment() { + + private val sharedViewModel: CreateSpaceViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = + FragmentSpaceCreateChooseTypeBinding.inflate(layoutInflater, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + views.publicButton.setOnClickListener(DebouncedClickListener({ + sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Public)) + })) + + views.privateButton.setOnClickListener(DebouncedClickListener({ + sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Private)) + })) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt new file mode 100644 index 0000000000..2dc36f8715 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding +import javax.inject.Inject + +class CreateSpaceDefaultRoomsFragment @Inject constructor( + private val epoxyController: SpaceDefaultRoomEpoxyController +) : VectorBaseFragment(), SpaceDefaultRoomEpoxyController.Listener { + + private val sharedViewModel: CreateSpaceViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = + FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + views.recyclerView.configureWith(epoxyController) + epoxyController.listener = this + + sharedViewModel.subscribe(this) { + epoxyController.setData(it) + } + + views.nextButton.debouncedClicks { + sharedViewModel.handle(CreateSpaceAction.NextFromDetails) + } + } + + // ----------------------------- + // Epoxy controller listener methods + // ----------------------------- +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt new file mode 100644 index 0000000000..506f71c92f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.activityViewModel +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding +import javax.inject.Inject + +class CreateSpaceDetailsFragment @Inject constructor( + private val epoxyController: SpaceDetailEpoxyController +) : VectorBaseFragment(), SpaceDetailEpoxyController.Listener { + + private val sharedViewModel: CreateSpaceViewModel by activityViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = + FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + views.recyclerView.configureWith(epoxyController) + epoxyController.listener = this + + sharedViewModel.subscribe(this) { + epoxyController.setData(it) + } + + views.nextButton.debouncedClicks { + sharedViewModel.handle(CreateSpaceAction.NextFromDetails) + } + } + + // ----------------------------- + // Epoxy controller listener methods + // ----------------------------- + + override fun onAvatarDelete() { + } + + override fun onAvatarChange() { + } + + override fun onNameChange(newName: String) { + sharedViewModel.handle(CreateSpaceAction.NameChanged(newName)) + } + + override fun onTopicChange(newTopic: String) { + sharedViewModel.handle(CreateSpaceAction.TopicChanged(newTopic)) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt new file mode 100644 index 0000000000..aca8452300 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import android.net.Uri +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.R +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.Session + +data class CreateSpaceState( + val name: String? = null, + val avatarUri: Uri? = null, + val topic: String = "", + val step: Step = Step.ChooseType, + val spaceType: SpaceType? = null, + val nameInlineError : String? = null, + val defaultRooms: List? = null +) : MvRxState { + + enum class Step { + ChooseType, + SetDetails, + AddRooms + } +} + +enum class SpaceType { + Public, + Private +} + +sealed class CreateSpaceAction : VectorViewModelAction { + data class SetRoomType(val type: SpaceType) : CreateSpaceAction() + data class NameChanged(val name: String) : CreateSpaceAction() + data class TopicChanged(val topic: String) : CreateSpaceAction() + object OnBackPressed : CreateSpaceAction() + object NextFromDetails : CreateSpaceAction() +} + +sealed class CreateSpaceEvents : VectorViewEvents { + object NavigateToDetails : CreateSpaceEvents() + object NavigateToChooseType : CreateSpaceEvents() + object NavigateToAddRooms : CreateSpaceEvents() + object Dismiss : CreateSpaceEvents() +} + +class CreateSpaceViewModel @AssistedInject constructor( + @Assisted initialState: CreateSpaceState, + private val session: Session, + private val stringProvider: StringProvider +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory { + fun create(initialState: CreateSpaceState): CreateSpaceViewModel + } + + companion object : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: CreateSpaceState): CreateSpaceViewModel? { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + override fun handle(action: CreateSpaceAction) { + when (action) { + is CreateSpaceAction.SetRoomType -> { + setState { + copy( + step = CreateSpaceState.Step.SetDetails, + spaceType = action.type + ) + } + _viewEvents.post(CreateSpaceEvents.NavigateToDetails) + } + is CreateSpaceAction.NameChanged -> { + setState { + copy( + nameInlineError = null, + name = action.name + ) + } + } + is CreateSpaceAction.TopicChanged -> { + setState { + copy( + topic = action.topic + ) + } + } + CreateSpaceAction.OnBackPressed -> { + handleBackNavigation() + } + CreateSpaceAction.NextFromDetails -> { + handleNextFromDetails() + } + }.exhaustive + } + + private fun handleBackNavigation() = withState { state -> + when (state.step) { + CreateSpaceState.Step.ChooseType -> { + _viewEvents.post(CreateSpaceEvents.Dismiss) + } + CreateSpaceState.Step.SetDetails -> { + setState { + copy( + step = CreateSpaceState.Step.ChooseType + ) + } + _viewEvents.post(CreateSpaceEvents.NavigateToChooseType) + } + CreateSpaceState.Step.AddRooms -> { + setState { + copy( + step = CreateSpaceState.Step.SetDetails + ) + } + _viewEvents.post(CreateSpaceEvents.NavigateToDetails) + } + } + } + + private fun handleNextFromDetails() = withState { state -> + if (state.name.isNullOrBlank()) { + setState { + copy( + nameInlineError = stringProvider.getString(R.string.create_space_error_empty_field_space_name) + ) + } + } else { + setState { + copy( + step = CreateSpaceState.Step.AddRooms + ) + } + _viewEvents.post(CreateSpaceEvents.NavigateToAddRooms) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt new file mode 100644 index 0000000000..05abcf95b0 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.GenericItem +import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.features.form.formEditTextItem +import javax.inject.Inject + +class SpaceDefaultRoomEpoxyController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +// private val avatarRenderer: AvatarRenderer +) : TypedEpoxyController() { + + var listener: Listener? = null + + override fun buildModels(data: CreateSpaceState?) { + genericFooterItem { + id("info_help_header") + style(GenericItem.STYLE.BIG_TEXT) + text(stringProvider.getString(R.string.create_spaces_room_public_header)) + textColor(colorProvider.getColorFromAttribute(R.attr.riot_primary_text_color)) + } + + genericFooterItem { + id("info_help") + text(stringProvider.getString(R.string.create_spaces_room_public_header_desc)) + textColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)) + } + + formEditTextItem { + id("roomName1") + enabled(true) + value(data?.name) + hint(stringProvider.getString(R.string.create_room_name_hint)) + showBottomSeparator(false) +// errorMessage(data?.nameInlineError) + onTextChange { text -> +// listener?.onNameChange(text) + } + } + + formEditTextItem { + id("roomName2") + enabled(true) +// value(data?.name) + hint(stringProvider.getString(R.string.create_room_name_hint)) + showBottomSeparator(false) +// errorMessage(data?.nameInlineError) + onTextChange { text -> +// listener?.onNameChange(text) + } + } + + formEditTextItem { + id("roomName3") + enabled(true) +// value(data?.name) + hint(stringProvider.getString(R.string.create_room_name_hint)) + showBottomSeparator(false) +// errorMessage(data?.nameInlineError) + onTextChange { text -> +// listener?.onNameChange(text) + } + } + } + + interface Listener { +// fun onAvatarDelete() +// fun onAvatarChange() +// fun onNameChange(newName: String) +// fun onTopicChange(newTopic: String) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt new file mode 100644 index 0000000000..357b741ff4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericFooterItem +import im.vector.app.features.form.formEditTextItem +import im.vector.app.features.form.formEditableSquareAvatarItem +import im.vector.app.features.form.formMultiLineEditTextItem +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.util.MatrixItem +import javax.inject.Inject + +class SpaceDetailEpoxyController @Inject constructor( + private val stringProvider: StringProvider, + private val avatarRenderer: AvatarRenderer +) : TypedEpoxyController() { + + var listener: Listener? = null + + override fun buildModels(data: CreateSpaceState?) { + genericFooterItem { + id("info_help") + text( + if (data?.spaceType == SpaceType.Public) { + stringProvider.getString(R.string.create_spaces_details_public_header) + } else { + stringProvider.getString(R.string.create_spaces_details_private_header) + } + ) + } + + formEditableSquareAvatarItem { + id("avatar") + enabled(true) + imageUri(data?.avatarUri) + avatarRenderer(avatarRenderer) + matrixItem(data?.name?.let { MatrixItem.RoomItem("", it, null).takeIf { !it.displayName.isNullOrBlank() } }) + clickListener { listener?.onAvatarChange() } + deleteListener { listener?.onAvatarDelete() } + } + + formEditTextItem { + id("name") + enabled(true) + value(data?.name) + hint(stringProvider.getString(R.string.create_room_name_hint)) + showBottomSeparator(false) + errorMessage(data?.nameInlineError) + onTextChange { text -> + listener?.onNameChange(text) + } + } + + formMultiLineEditTextItem { + id("topic") + enabled(true) + value(data?.topic) + hint(stringProvider.getString(R.string.create_room_topic_hint)) + showBottomSeparator(false) + textSizeSp(15) + onTextChange { text -> + listener?.onTopicChange(text) + } + } + } + + interface Listener { + fun onAvatarDelete() + fun onAvatarChange() + fun onNameChange(newName: String) + fun onTopicChange(newTopic: String) + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt new file mode 100644 index 0000000000..552a98ded2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.util.TypedValue +import androidx.appcompat.content.res.AppCompatResources.getDrawable +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes +import im.vector.app.R +import im.vector.app.databinding.ViewSpaceTypeButtonBinding + +class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) + : ConstraintLayout(context, attrs, defStyle) { + + private val views: ViewSpaceTypeButtonBinding + + var title: String? = null + set(value) { + if (value != title) { + field = value + views.title.text = value + } + } + + var subTitle: String? = null + set(value) { + if (value != subTitle) { + field = value + views.subTitle.text = value + } + } + + var icon: Drawable? = null + set(value) { + if (value != icon) { + field = value + views.buttonImageView.setImageDrawable(value) + } + } + +// private var tint: Int? = null +// set(value) { +// field = value +// if (value != null) { +// views.buttonImageView.imageTintList = ColorStateList.valueOf(value) +// } else { +// views.buttonImageView.clearColorFilter() +// } +// } + +// var action: (() -> Unit)? = null + + init { + inflate(context, R.layout.view_space_type_button, this) + views = ViewSpaceTypeButtonBinding.bind(this) + + if (isInEditMode) { + title = "Title" + subTitle = "This is doing something" + } + + context.withStyledAttributes(attrs, R.styleable.WizardButtonView) { + title = getString(R.styleable.WizardButtonView_title) ?: "" + subTitle = getString(R.styleable.WizardButtonView_subTitle) ?: "" + icon = getDrawable(R.styleable.WizardButtonView_icon) +// tint = getColor(R.styleable.WizardButtonView_iconTint, ThemeUtils.getColor(context, R.attr.riotx_text_primary)) + } + + val outValue = TypedValue() + context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + this.foreground = getDrawable(context, outValue.resourceId) + } + +// views.content.isClickable = true +// views.content.isFocusable = true +// views.content.setOnClickListener { +// action?.invoke() +// } + } +} diff --git a/vector/src/main/res/drawable/ic_camera_plain.xml b/vector/src/main/res/drawable/ic_camera_plain.xml new file mode 100644 index 0000000000..56d55c9da1 --- /dev/null +++ b/vector/src/main/res/drawable/ic_camera_plain.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_public_room.xml b/vector/src/main/res/drawable/ic_public_room.xml new file mode 100644 index 0000000000..1520898831 --- /dev/null +++ b/vector/src/main/res/drawable/ic_public_room.xml @@ -0,0 +1,13 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_room_private.xml b/vector/src/main/res/drawable/ic_room_private.xml new file mode 100644 index 0000000000..cacdf15a3b --- /dev/null +++ b/vector/src/main/res/drawable/ic_room_private.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/fragment_space_create_choose_type.xml b/vector/src/main/res/layout/fragment_space_create_choose_type.xml new file mode 100644 index 0000000000..ddf61aabf8 --- /dev/null +++ b/vector/src/main/res/layout/fragment_space_create_choose_type.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml b/vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml new file mode 100644 index 0000000000..3097664e02 --- /dev/null +++ b/vector/src/main/res/layout/fragment_space_create_generic_epoxy_form.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_editable_square_avatar.xml b/vector/src/main/res/layout/item_editable_square_avatar.xml new file mode 100644 index 0000000000..b3ec057fd4 --- /dev/null +++ b/vector/src/main/res/layout/item_editable_square_avatar.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/view_space_type_button.xml b/vector/src/main/res/layout/view_space_type_button.xml new file mode 100644 index 0000000000..1ccbed3201 --- /dev/null +++ b/vector/src/main/res/layout/view_space_type_button.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/attrs.xml b/vector/src/main/res/values/attrs.xml index ecc64bf07d..86105cf74d 100644 --- a/vector/src/main/res/values/attrs.xml +++ b/vector/src/main/res/values/attrs.xml @@ -54,8 +54,10 @@ + + - + @@ -68,4 +70,12 @@ + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 01adc31bb1..b2bfe76f57 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3259,4 +3259,22 @@ Messages failed to send Delete unsent messages Are you sure you want to delete all unsent messages in this room? + + Add Space + Your public space + Spaces are a new way to group rooms and people + What type of space do you want to create? + To join an existing space, you need an invite. + Public + Open to anyone, best for communities + Private + Invite only, best for yourself or teams + Create a space + Add some details to help it stand out. You can change these at any point. + Add some details to help people identify it. You can change these at any point. + Give it a name to continue. + What are some discussions you want to have in Runner’s World? + We’ll create rooms for them, and auto-join everyone. You can add more later too. + + From 6c69a6055da7fa45f4a7702696a76c7366b64707 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 25 Feb 2021 00:17:27 +0100 Subject: [PATCH 109/230] Support retry after M_LIMIT_EXCEEDED --- .../sdk/internal/task/ConfigurableTask.kt | 6 +++-- .../matrix/android/sdk/internal/task/Task.kt | 22 +++++++++++++++++++ .../android/sdk/internal/task/TaskExecutor.kt | 6 ++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt index 97f9a0dd51..bc80cf7ee8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt @@ -37,7 +37,8 @@ internal data class ConfigurableTask( val id: UUID, val callbackThread: TaskThread, val executionThread: TaskThread, - val callback: MatrixCallback + val callback: MatrixCallback, + val maxRetryCount: Int = 0 ) : Task by task { @@ -57,7 +58,8 @@ internal data class ConfigurableTask( id = id, callbackThread = callbackThread, executionThread = executionThread, - callback = callback + callback = callback, + maxRetryCount = retryCount ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt index a6c80a0b1a..a5d031e02a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt @@ -16,7 +16,29 @@ package org.matrix.android.sdk.internal.task +import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.shouldBeRetried +import timber.log.Timber + internal interface Task { suspend fun execute(params: PARAMS): RESULT + + suspend fun executeRetry(params: PARAMS, remainingRetry: Int) : RESULT { + return try { + execute(params) + } catch (failure: Throwable) { + if (failure.shouldBeRetried() && remainingRetry > 0) { + Timber.d(failure, "## TASK: Retriable error") + if (failure is Failure.ServerError) { + val waitTime = failure.error.retryAfterMillis ?: 0L + Timber.d(failure, "## TASK: Quota wait time $waitTime") + delay(waitTime + 100) + } + return executeRetry(params, remainingRetry - 1) + } + throw failure + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt index 478a356432..4da16eff22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt @@ -40,9 +40,9 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers .launch(task.callbackThread.toDispatcher()) { val resultOrFailure = runCatching { withContext(task.executionThread.toDispatcher()) { - Timber.v("Enqueue task $task") - Timber.v("Execute task $task on ${Thread.currentThread().name}") - task.execute(task.params) + Timber.v("## TASK: Enqueue task $task") + Timber.v("## TASK: Execute task $task on ${Thread.currentThread().name}") + task.executeRetry(task.params, task.maxRetryCount) } } resultOrFailure From 7d2d7b411e0efc7069932cafe4b7a1549d08dd2b Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 25 Feb 2021 00:23:53 +0100 Subject: [PATCH 110/230] Space Create Wizard Flow --- .../session/room/failure/CreateRoomFailure.kt | 2 +- .../android/sdk/api/session/space/Space.kt | 6 ++ .../session/room/DefaultRoomService.kt | 9 +- .../sdk/internal/session/room/SpaceGetter.kt | 39 ++++++++ .../session/room/create/CreateRoomTask.kt | 2 +- .../session/room/state/DefaultStateService.kt | 2 +- .../internal/session/space/CreateSpaceTask.kt | 56 +++++++++++ .../internal/session/space/DefaultSpace.kt | 8 +- .../session/space/DefaultSpaceService.kt | 38 +++----- .../sdk/internal/session/space/SpaceModule.kt | 8 ++ .../app/features/form/FormEditTextItem.kt | 9 ++ .../form/FormEditableSquareAvatarItem.kt | 31 ++++-- .../grouplist/HomeSpaceSummaryItem.kt | 4 + .../vector/app/features/home/HomeActivity.kt | 21 +++- .../features/navigation/DefaultNavigator.kt | 21 ++++ .../app/features/navigation/Navigator.kt | 2 + .../features/spaces/SpaceCreationActivity.kt | 29 +++++- .../features/spaces/SpacesListViewModel.kt | 15 ++- .../spaces/create/ChooseSpaceTypeFragment.kt | 2 +- .../create/CreateSpaceDefaultRoomsFragment.kt | 6 +- .../create/CreateSpaceDetailsFragment.kt | 16 +++- .../spaces/create/CreateSpaceViewModel.kt | 94 +++++++++++++++++- .../spaces/create/CreateSpaceViewModelTask.kt | 95 +++++++++++++++++++ .../create/SpaceDefaultRoomEpoxyController.kt | 26 +++-- .../spaces/create/WizardButtonView.kt | 3 +- vector/src/main/res/values/ids.xml | 3 + vector/src/main/res/values/strings.xml | 2 + 27 files changed, 481 insertions(+), 68 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt create mode 100644 vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt create mode 100644 vector/src/main/res/values/ids.xml 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 208cdd4556..deab0ca3e7 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 @@ -21,7 +21,7 @@ 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 CreatedWithTimeout(val roomID: String) : CreateRoomFailure() data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 01a0dbc929..0172b3701b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -17,11 +17,17 @@ package org.matrix.android.sdk.api.session.space import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.RoomSummary interface Space { fun asRoom() : Room + /** + * A current snapshot of [RoomSummary] associated with the room + */ + fun spaceSummary(): SpaceSummary? + suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean = false) suspend fun removeRoom(roomId: String) 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 22f61bc517..4724167e87 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 @@ -66,8 +66,13 @@ internal class DefaultRoomService @Inject constructor( private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource ) : RoomService { - override suspend fun createRoom(createRoomParams: CreateRoomParams): String { - return createRoomTask.execute(createRoomParams) + override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable { + return createRoomTask + .configureWith(createRoomParams) { + this.callback = callback + this.retryCount = 3 + } + .executeBy(taskExecutor) } override fun getRoom(roomId: String): Room? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt new file mode 100644 index 0000000000..f440a67710 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2021 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 + +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.internal.session.space.DefaultSpace +import org.matrix.android.sdk.internal.session.space.SpaceSummaryDataSource +import javax.inject.Inject + +internal interface SpaceGetter { + fun get(spaceId: String): Space? +} + +internal class DefaultSpaceGetter @Inject constructor( + private val roomGetter: RoomGetter, + private val spaceSummaryDataSource: SpaceSummaryDataSource +) : SpaceGetter { + + override fun get(spaceId: String): Space? { + return roomGetter.getRoom(spaceId) + ?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE } + ?.let { DefaultSpace(it, spaceSummaryDataSource) } + } +} 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 bafe2b90ae..de6a71e581 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 @@ -102,7 +102,7 @@ internal class DefaultCreateRoomTask @Inject constructor( .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } } catch (exception: TimeoutCancellationException) { - throw CreateRoomFailure.CreatedWithTimeout + throw CreateRoomFailure.CreatedWithTimeout(roomId) } Realm.getInstance(realmConfiguration).executeTransactionAsync { 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 615bc99096..4948e3ace5 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 @@ -73,7 +73,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private eventType = eventType, body = body.toSafeJson(eventType) ) - sendStateTask.execute(params) + sendStateTask.executeRetry(params, 3) } private fun JsonDict.toSafeJson(eventType: String): JsonDict { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt new file mode 100644 index 0000000000..5f174587d0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2021 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.space + +import io.realm.RealmConfiguration +import kotlinx.coroutines.TimeoutCancellationException +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.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.task.Task +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +/** + * A simple wrapper of create room task that adds waiting for DB entities of spaces + */ +internal interface CreateSpaceTask : Task + +internal class DefaultCreateSpaceTask @Inject constructor( + private val createRoomTask: CreateRoomTask, + @SessionDatabase private val realmConfiguration: RealmConfiguration +) : CreateSpaceTask { + + override suspend fun execute(params: CreateRoomParams): String { + val spaceId = createRoomTask.execute(params) + + try { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(SpaceSummaryEntity::class.java) + .equalTo(SpaceSummaryEntityFields.SPACE_ID, spaceId) + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(spaceId) + } + + return spaceId + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 264cfd44ed..efba103ab7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -22,15 +22,19 @@ import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.model.SpaceChildContent -import java.lang.IllegalArgumentException -class DefaultSpace(private val room: Room) : Space { +internal class DefaultSpace(private val room: Room, private val spaceSummaryDataSource: SpaceSummaryDataSource) : Space { override fun asRoom(): Room { return room } + override fun spaceSummary(): SpaceSummary? { + return spaceSummaryDataSource.getSpaceSummary(asRoom().roomId) + } + override suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean) { asRoom().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 906886d5e8..0210c81e5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -22,7 +22,6 @@ import com.zhuinden.monarchy.Monarchy 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.RoomSummary -import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.space.CreateSpaceParams @@ -32,40 +31,33 @@ import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.session.space.model.SpaceChildContent import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.room.RoomGetter -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 -import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask +import org.matrix.android.sdk.internal.session.room.SpaceGetter import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask -import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult -import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask -import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject internal class DefaultSpaceService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, - private val createRoomTask: CreateRoomTask, - private val joinRoomTask: JoinRoomTask, + private val createSpaceTask: CreateSpaceTask, +// private val joinRoomTask: JoinRoomTask, private val joinSpaceTask: JoinSpaceTask, - private val markAllRoomsReadTask: MarkAllRoomsReadTask, - private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, - private val roomIdByAliasTask: GetRoomIdByAliasTask, - private val deleteRoomAliasTask: DeleteRoomAliasTask, - private val roomGetter: RoomGetter, + private val spaceGetter: SpaceGetter, +// private val markAllRoomsReadTask: MarkAllRoomsReadTask, +// private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, +// private val roomIdByAliasTask: GetRoomIdByAliasTask, +// private val deleteRoomAliasTask: DeleteRoomAliasTask, +// private val roomGetter: RoomGetter, private val spaceSummaryDataSource: SpaceSummaryDataSource, private val peekSpaceTask: PeekSpaceTask, private val resolveSpaceInfoTask: ResolveSpaceInfoTask, - private val leaveRoomTask: LeaveRoomTask, - private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, - private val taskExecutor: TaskExecutor + private val leaveRoomTask: LeaveRoomTask +// private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, +// private val taskExecutor: TaskExecutor ) : SpaceService { override suspend fun createSpace(params: CreateSpaceParams): String { - return createRoomTask.execute(params) + return createSpaceTask.executeRetry(params, 3) } override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String { @@ -78,9 +70,7 @@ internal class DefaultSpaceService @Inject constructor( } override fun getSpace(spaceId: String): Space? { - return roomGetter.getRoom(spaceId) - ?.takeIf { it.roomSummary()?.roomType == RoomType.SPACE } - ?.let { DefaultSpace(it) } + return spaceGetter.get(spaceId) } override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt index 4612d9e142..84a2d5267f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt @@ -20,6 +20,8 @@ import dagger.Binds import dagger.Module import dagger.Provides import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.room.DefaultSpaceGetter +import org.matrix.android.sdk.internal.session.room.SpaceGetter import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import retrofit2.Retrofit @@ -45,4 +47,10 @@ internal abstract class SpaceModule { @Binds abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask + + @Binds + abstract fun bindSpaceGetter(getter: DefaultSpaceGetter): SpaceGetter + + @Binds + abstract fun bindCreateSpaceTask(getter: DefaultCreateSpaceTask): CreateSpaceTask } 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 68e2e6b371..a1bd2bd1a3 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 @@ -18,6 +18,7 @@ package im.vector.app.features.form import android.text.Editable import android.view.View +import android.view.inputmethod.EditorInfo import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -50,6 +51,12 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var inputType: Int? = null + @EpoxyAttribute + var singleLine: Boolean? = null + + @EpoxyAttribute + var imeOptions: Int? = null + @EpoxyAttribute var onTextChange: ((String) -> Unit)? = null @@ -69,6 +76,8 @@ abstract class FormEditTextItem : VectorEpoxyModel() { holder.textInputEditText.setTextSafe(value) holder.textInputEditText.isEnabled = enabled inputType?.let { holder.textInputEditText.inputType = it } + holder.textInputEditText.isSingleLine = singleLine ?: false + holder.textInputEditText.imeOptions = imeOptions ?: EditorInfo.IME_ACTION_NONE holder.textInputEditText.addTextChangedListener(onTextChangeListener) holder.bottomSeparator.isVisible = showBottomSeparator diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt index 5023c1b483..cbb545825d 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditableSquareAvatarItem.kt @@ -16,13 +16,16 @@ package im.vector.app.features.form import android.net.Uri +import android.util.TypedValue import android.view.View import android.widget.ImageView import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelWithHolder -import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.load.MultiTransformation +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder @@ -55,13 +58,24 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder { + val corner = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 8f, + holder.view.resources.displayMetrics + ).toInt() + GlideApp.with(holder.image) + .load(imageUri) + .transform(MultiTransformation(CenterCrop(), RoundedCorners(corner))) + .into(holder.image) + } + matrixItem != null -> { + avatarRenderer?.renderSpace(matrixItem!!, holder.image) + } + else -> { + avatarRenderer?.clear(holder.image) + } } holder.delete.isVisible = enabled && (imageUri != null || matrixItem?.avatarUrl?.isNotEmpty() == true) holder.delete.onClick(deleteListener?.takeIf { enabled }) @@ -72,6 +86,7 @@ abstract class FormEditableSquareAvatarItem : EpoxyModelWithHolder(R.id.itemEditableAvatarImageContainer) val image by bind(R.id.itemEditableAvatarImage) diff --git a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt index ade86a9d89..c3f459f49e 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt @@ -35,6 +35,10 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel Unit)? = null + override fun getViewType(): Int { + // mm.. it's reusing the same layout for basic space item + return R.id.space_item_home + } override fun bind(holder: Holder) { super.bind(holder) holder.rootView.setOnClickListener { listener?.invoke() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 982b59263d..40d0cdd622 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home +import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri @@ -36,6 +37,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.hideKeyboard +import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity @@ -103,6 +105,23 @@ class HomeActivity : @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter + private val createSpaceResultLauncher = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + val spaceId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_CREATED_SPACE_ID) + val defaultRoomsId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_DEFAULT_ROOM_ID) + views.drawerLayout.closeDrawer(GravityCompat.START) + + // Here we want to change current space to the newly created one, and then immediatly open the default room + if (spaceId != null) { + navigator.switchToSpace(this, spaceId, defaultRoomsId) + } + + // Also we should show the share space bottomsheet + } else { + // viewModel.handle(CrossSigningSettingsAction.ReAuthCancelled) + } + } + private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerStateChanged(newState: Int) { hideKeyboard() @@ -147,7 +166,7 @@ class HomeActivity : startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId)) } is HomeActivitySharedAction.AddSpace -> { - startActivity(SpaceCreationActivity.newIntent(this)) + createSpaceResultLauncher.launch(SpaceCreationActivity.newIntent(this)) } }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 76e4cad28f..d6eeade97e 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -29,6 +29,7 @@ import androidx.core.app.ActivityOptionsCompat import androidx.core.app.TaskStackBuilder import androidx.core.util.Pair import androidx.core.view.ViewCompat +import arrow.core.Option import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError @@ -46,6 +47,7 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.features.devtools.RoomDevToolActivity +import im.vector.app.features.grouplist.SelectedSpaceDataSource import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.RoomDetailArgs import im.vector.app.features.home.room.detail.search.SearchActivity @@ -77,6 +79,7 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryDat import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -85,6 +88,7 @@ class DefaultNavigator @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val vectorPreferences: VectorPreferences, private val widgetArgsBuilder: WidgetArgsBuilder, + private val selectedSpaceDataSource: SelectedSpaceDataSource, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider ) : Navigator { @@ -98,6 +102,23 @@ class DefaultNavigator @Inject constructor( startActivity(context, intent, buildTask) } + override fun switchToSpace(context: Context, spaceId: String, roomId: String?) { + if (sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId) == null) { + fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast()) + return + } + + sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId)?.spaceSummary()?.let { + Timber.d("## Nav: Switching to space $spaceId / ${it.roomSummary.name}") + selectedSpaceDataSource.post(Option.just(it)) + } ?: kotlin.run { + Timber.d("## Nav: Failed to switch to space $spaceId") + } + if (roomId != null) { + openRoom(context, roomId) + } + } + override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) { val session = sessionHolder.getSafeActiveSession() ?: return val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index b4bd677b0c..fcd35be162 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -38,6 +38,8 @@ interface Navigator { fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false) + fun switchToSpace(context: Context, spaceId: String, roomId: String?) + fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) fun requestSessionVerification(context: Context, otherSessionId: String) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index 65c8102020..ed8b85f587 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -20,7 +20,9 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.viewModel import com.airbnb.mvrx.withState import im.vector.app.R @@ -91,6 +93,23 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac CreateSpaceEvents.NavigateToAddRooms -> { navigateToFragment(CreateSpaceDefaultRoomsFragment::class.java) } + is CreateSpaceEvents.ShowModalError -> { + hideWaitingView() + AlertDialog.Builder(this) + .setMessage(it.errorMessage) + .setPositiveButton(getString(R.string.ok), null) + .show() + } + is CreateSpaceEvents.FinishSuccess -> { + setResult(RESULT_OK, Intent().apply { + putExtra(RESULT_DATA_CREATED_SPACE_ID, it.spaceId) + putExtra(RESULT_DATA_DEFAULT_ROOM_ID, it.defaultRoomId) + }) + finish() + } + CreateSpaceEvents.HideModalLoading -> { + hideWaitingView() + } } } } @@ -114,16 +133,24 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac val titleRes = when (state.step) { CreateSpaceState.Step.ChooseType -> R.string.activity_create_space_title CreateSpaceState.Step.SetDetails -> R.string.your_public_space - CreateSpaceState.Step.AddRooms -> R.string.your_public_space + CreateSpaceState.Step.AddRooms -> R.string.your_public_space } supportActionBar?.let { it.title = getString(titleRes) } ?: run { setTitle(getString(titleRes)) } + + if (state.creationResult is Loading) { + showWaitingView(getString(R.string.create_spaces_loading_message)) + } } companion object { + + const val RESULT_DATA_CREATED_SPACE_ID = "RESULT_DATA_CREATED_SPACE_ID" + const val RESULT_DATA_DEFAULT_ROOM_ID = "RESULT_DATA_DEFAULT_ROOM_ID" + fun newIntent(context: Context): Intent { return Intent(context, SpaceCreationActivity::class.java).apply { // putExtra(MvRx.KEY_ARG, SpaceDirectoryArgs(spaceId)) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index b8089d6f98..4ec6e7d4ad 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -86,8 +86,15 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp private var currentGroupId = "" init { - observeGroupSummaries() + observeSpaceSummaries() observeSelectionState() + selectedSpaceDataSource.observe().execute { + if (this.selectedSpace != it.invoke()?.orNull()) { + copy( + selectedSpace = it.invoke()?.orNull() + ) + } else this + } } private fun observeSelectionState() { @@ -143,8 +150,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp _viewEvents.post(SpaceListViewEvents.AddSpace) } - private fun observeGroupSummaries() { - val roomSummaryQueryParams = roomSummaryQueryParams() { + private fun observeSpaceSummaries() { + val spaceSummaryQueryParams = roomSummaryQueryParams() { memberships = listOf(Membership.JOIN, Membership.INVITE) displayName = QueryStringValue.IsNotEmpty excludeType = listOf(/**RoomType.MESSAGING,$*/ @@ -171,7 +178,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp }, session .rx() - .liveSpaceSummaries(roomSummaryQueryParams), + .liveSpaceSummaries(spaceSummaryQueryParams), BiFunction { allCommunityGroup, communityGroups -> listOf(allCommunityGroup) + communityGroups } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt index 080bb99ea4..b174952ec6 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt @@ -43,7 +43,7 @@ class ChooseSpaceTypeFragment @Inject constructor( })) views.privateButton.setOnClickListener(DebouncedClickListener({ - sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Private)) + // sharedViewModel.handle(CreateSpaceAction.SetRoomType(SpaceType.Private)) })) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt index 2dc36f8715..fb1ed8e5f8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDefaultRoomsFragment.kt @@ -46,10 +46,14 @@ class CreateSpaceDefaultRoomsFragment @Inject constructor( } views.nextButton.debouncedClicks { - sharedViewModel.handle(CreateSpaceAction.NextFromDetails) + sharedViewModel.handle(CreateSpaceAction.NextFromDefaultRooms) } } + override fun onNameChange(index: Int, newName: String) { + sharedViewModel.handle(CreateSpaceAction.DefaultRoomNameChanged(index, newName)) + } + // ----------------------------- // Epoxy controller listener methods // ----------------------------- diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt index 506f71c92f..670876fdf1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceDetailsFragment.kt @@ -16,25 +16,32 @@ package im.vector.app.features.spaces.create +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.activityViewModel +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.resources.ColorProvider import im.vector.app.databinding.FragmentSpaceCreateGenericEpoxyFormBinding import javax.inject.Inject class CreateSpaceDetailsFragment @Inject constructor( - private val epoxyController: SpaceDetailEpoxyController -) : VectorBaseFragment(), SpaceDetailEpoxyController.Listener { + private val epoxyController: SpaceDetailEpoxyController, + private val colorProvider: ColorProvider +) : VectorBaseFragment(), SpaceDetailEpoxyController.Listener, + GalleryOrCameraDialogHelper.Listener { private val sharedViewModel: CreateSpaceViewModel by activityViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceCreateGenericEpoxyFormBinding.inflate(layoutInflater, container, false) + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -50,14 +57,19 @@ class CreateSpaceDetailsFragment @Inject constructor( } } + override fun onImageReady(uri: Uri?) { + sharedViewModel.handle(CreateSpaceAction.SetAvatar(uri)) + } // ----------------------------- // Epoxy controller listener methods // ----------------------------- override fun onAvatarDelete() { + sharedViewModel.handle(CreateSpaceAction.SetAvatar(null)) } override fun onAvatarChange() { + galleryOrCameraDialogHelper.show() } override fun onNameChange(newName: String) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index aca8452300..cd2680ef5f 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -17,20 +17,29 @@ package im.vector.app.features.spaces.create import android.net.Uri +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.resources.StringProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session data class CreateSpaceState( @@ -39,8 +48,9 @@ data class CreateSpaceState( val topic: String = "", val step: Step = Step.ChooseType, val spaceType: SpaceType? = null, - val nameInlineError : String? = null, - val defaultRooms: List? = null + val nameInlineError: String? = null, + val defaultRooms: Map? = null, + val creationResult: Async = Uninitialized ) : MvRxState { enum class Step { @@ -59,8 +69,11 @@ sealed class CreateSpaceAction : VectorViewModelAction { data class SetRoomType(val type: SpaceType) : CreateSpaceAction() data class NameChanged(val name: String) : CreateSpaceAction() data class TopicChanged(val topic: String) : CreateSpaceAction() + data class SetAvatar(val uri: Uri?) : CreateSpaceAction() object OnBackPressed : CreateSpaceAction() object NextFromDetails : CreateSpaceAction() + object NextFromDefaultRooms : CreateSpaceAction() + data class DefaultRoomNameChanged(val index: Int, val name: String) : CreateSpaceAction() } sealed class CreateSpaceEvents : VectorViewEvents { @@ -68,12 +81,17 @@ sealed class CreateSpaceEvents : VectorViewEvents { object NavigateToChooseType : CreateSpaceEvents() object NavigateToAddRooms : CreateSpaceEvents() object Dismiss : CreateSpaceEvents() + data class FinishSuccess(val spaceId: String, val defaultRoomId: String?) : CreateSpaceEvents() + data class ShowModalError(val errorMessage: String) : CreateSpaceEvents() + object HideModalLoading : CreateSpaceEvents() } class CreateSpaceViewModel @AssistedInject constructor( @Assisted initialState: CreateSpaceState, private val session: Session, - private val stringProvider: StringProvider + private val stringProvider: StringProvider, + private val createSpaceViewModelTask: CreateSpaceViewModelTask, + private val errorFormatter: ErrorFormatter ) : VectorViewModel(initialState) { @AssistedFactory @@ -90,6 +108,12 @@ class CreateSpaceViewModel @AssistedInject constructor( } return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } + + override fun initialState(viewModelContext: ViewModelContext): CreateSpaceState? { + return CreateSpaceState( + defaultRooms = mapOf(0 to viewModelContext.activity.getString(R.string.create_spaces_default_public_room_name)) + ) + } } override fun handle(action: CreateSpaceAction) { @@ -124,6 +148,21 @@ class CreateSpaceViewModel @AssistedInject constructor( CreateSpaceAction.NextFromDetails -> { handleNextFromDetails() } + CreateSpaceAction.NextFromDefaultRooms -> { + handleNextFromDefaultRooms() + } + is CreateSpaceAction.DefaultRoomNameChanged -> { + setState { + copy( + defaultRooms = (defaultRooms ?: emptyMap()).toMutableMap().apply { + this[action.index] = action.name + } + ) + } + } + is CreateSpaceAction.SetAvatar -> { + setState { copy(avatarUri = action.uri) } + } }.exhaustive } @@ -167,4 +206,53 @@ class CreateSpaceViewModel @AssistedInject constructor( _viewEvents.post(CreateSpaceEvents.NavigateToAddRooms) } } + + private fun handleNextFromDefaultRooms() = withState { state -> + val spaceName = state.name ?: return@withState + setState { + copy(creationResult = Loading()) + } + viewModelScope.launch(Dispatchers.IO) { + try { + val result = createSpaceViewModelTask.execute( + CreateSpaceTaskParams( + spaceName = spaceName, + spaceTopic = state.topic, + spaceAvatar = state.avatarUri, + isPublic = state.spaceType == SpaceType.Public, + defaultRooms = state.defaultRooms + ?.entries + ?.sortedBy { it.key } + ?.mapNotNull { it.value } ?: emptyList() + ) + ) + when (result) { + is CreateSpaceTaskResult.Success -> { + setState { + copy(creationResult = Success(result.spaceId)) + } + _viewEvents.post(CreateSpaceEvents.FinishSuccess(result.spaceId, result.childIds.firstOrNull())) + } + is CreateSpaceTaskResult.PartialSuccess -> { + // XXX what can we do here? + setState { + copy(creationResult = Success(result.spaceId)) + } + _viewEvents.post(CreateSpaceEvents.FinishSuccess(result.spaceId, result.childIds.firstOrNull())) + } + is CreateSpaceTaskResult.FailedToCreateSpace -> { + setState { + copy(creationResult = Fail(result.failure)) + } + _viewEvents.post(CreateSpaceEvents.ShowModalError(errorFormatter.toHumanReadable(result.failure))) + } + } + } catch (failure: Throwable) { + setState { + copy(creationResult = Fail(failure)) + } + _viewEvents.post(CreateSpaceEvents.ShowModalError(errorFormatter.toHumanReadable(failure))) + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt new file mode 100644 index 0000000000..a565e290f6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModelTask.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 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.spaces.create + +import android.net.Uri +import im.vector.app.core.platform.ViewModelTask +import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.Session +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 +import org.matrix.android.sdk.internal.util.awaitCallback +import timber.log.Timber +import javax.inject.Inject + +sealed class CreateSpaceTaskResult { + + data class Success(val spaceId: String, val childIds: List) : CreateSpaceTaskResult() + + data class PartialSuccess(val spaceId: String, val childIds: List, val failedRooms: Map) : CreateSpaceTaskResult() + + class FailedToCreateSpace(val failure: Throwable) : CreateSpaceTaskResult() +} + +data class CreateSpaceTaskParams( + val spaceName: String, + val spaceTopic: String?, + val spaceAvatar: Uri? = null, + val isPublic: Boolean, + val defaultRooms: List = emptyList() +) + +class CreateSpaceViewModelTask @Inject constructor( + private val session: Session, + private val stringProvider: StringProvider +) : ViewModelTask { + + override suspend fun execute(params: CreateSpaceTaskParams): CreateSpaceTaskResult { + val spaceID = try { + session.spaceService().createSpace(params.spaceName, params.spaceTopic, params.spaceAvatar, params.isPublic) + } catch (failure: Throwable) { + return CreateSpaceTaskResult.FailedToCreateSpace(failure) + } + + val createdSpace = session.spaceService().getSpace(spaceID) + + val childErrors = mutableMapOf() + val childIds = mutableListOf() + if (params.isPublic) { + params.defaultRooms + .filter { it.isNotBlank() } + .forEach { roomName -> + try { + val roomId = try { + awaitCallback { + session.createRoom(CreateRoomParams().apply { + this.name = roomName + this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + }, it) + } + } catch (timeout: CreateRoomFailure.CreatedWithTimeout) { + // we ignore that? + timeout.roomID + } + val via = session.sessionParams.homeServerHost?.let { listOf(it) } ?: emptyList() + createdSpace!!.addChildren(roomId, via, null, true) + childIds.add(roomId) + } catch (failure: Throwable) { + Timber.d("Failed to create child room in $spaceID") + childErrors[roomName] = failure + } + } + } + + return if (childErrors.isEmpty()) { + CreateSpaceTaskResult.Success(spaceID, childIds) + } else { + CreateSpaceTaskResult.PartialSuccess(spaceID, childIds, childErrors) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt index 05abcf95b0..27b57713ba 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt @@ -28,7 +28,6 @@ import javax.inject.Inject class SpaceDefaultRoomEpoxyController @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider -// private val avatarRenderer: AvatarRenderer ) : TypedEpoxyController() { var listener: Listener? = null @@ -50,44 +49,41 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( formEditTextItem { id("roomName1") enabled(true) - value(data?.name) - hint(stringProvider.getString(R.string.create_room_name_hint)) + value(data?.defaultRooms?.get(0)) + hint(stringProvider.getString(R.string.create_room_name_section)) showBottomSeparator(false) -// errorMessage(data?.nameInlineError) onTextChange { text -> -// listener?.onNameChange(text) + listener?.onNameChange(0, text) } } formEditTextItem { id("roomName2") enabled(true) -// value(data?.name) - hint(stringProvider.getString(R.string.create_room_name_hint)) + value(data?.defaultRooms?.get(1)) + hint(stringProvider.getString(R.string.create_room_name_section)) showBottomSeparator(false) -// errorMessage(data?.nameInlineError) onTextChange { text -> -// listener?.onNameChange(text) + listener?.onNameChange(1, text) } } formEditTextItem { id("roomName3") enabled(true) -// value(data?.name) - hint(stringProvider.getString(R.string.create_room_name_hint)) + value(data?.defaultRooms?.get(2)) + hint(stringProvider.getString(R.string.create_room_name_section)) showBottomSeparator(false) -// errorMessage(data?.nameInlineError) onTextChange { text -> -// listener?.onNameChange(text) + listener?.onNameChange(2, text) } } } interface Listener { -// fun onAvatarDelete() + // fun onAvatarDelete() // fun onAvatarChange() -// fun onNameChange(newName: String) + fun onNameChange(index: Int, newName: String) // fun onTopicChange(newTopic: String) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt index 552a98ded2..066c329c34 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt @@ -18,6 +18,7 @@ package im.vector.app.features.spaces.create import android.content.Context import android.graphics.drawable.Drawable +import android.os.Build import android.util.AttributeSet import android.util.TypedValue import androidx.appcompat.content.res.AppCompatResources.getDrawable @@ -85,7 +86,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib val outValue = TypedValue() context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true) - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { this.foreground = getDrawable(context, outValue.resourceId) } diff --git a/vector/src/main/res/values/ids.xml b/vector/src/main/res/values/ids.xml new file mode 100644 index 0000000000..099d6f4279 --- /dev/null +++ b/vector/src/main/res/values/ids.xml @@ -0,0 +1,3 @@ + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b2bfe76f57..fb578f3802 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3275,6 +3275,8 @@ Give it a name to continue. What are some discussions you want to have in Runner’s World? We’ll create rooms for them, and auto-join everyone. You can add more later too. + General + Creating Space… From a901e1d1791c9e33019ef966d8c395eddc8440e2 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 25 Feb 2021 18:22:16 +0100 Subject: [PATCH 111/230] Post Creation Share flow --- .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../grouplist/HomeSpaceSummaryItem.kt | 3 + .../vector/app/features/home/HomeActivity.kt | 8 +- .../home/room/detail/RoomDetailFragment.kt | 164 ++++++++++-------- .../features/navigation/DefaultNavigator.kt | 11 +- .../app/features/navigation/Navigator.kt | 4 +- .../features/permalink/PermalinkHandler.kt | 53 +++--- .../features/spaces/ShareSpaceBottomSheet.kt | 107 ++++++++++++ .../app/features/spaces/SpaceListFragment.kt | 4 + .../features/spaces/SpaceSummaryController.kt | 3 + .../app/features/spaces/SpaceSummaryItem.kt | 13 ++ .../features/spaces/SpacesListViewModel.kt | 39 ++++- .../spaces/create/WizardButtonView.kt | 31 ++-- vector/src/main/res/drawable/ic_mail.xml | 21 +++ .../src/main/res/drawable/ic_share_link.xml | 12 ++ .../res/layout/bottom_sheet_space_invite.xml | 79 +++++++++ vector/src/main/res/layout/item_space.xml | 17 +- .../res/layout/view_space_type_button.xml | 6 +- vector/src/main/res/values/strings.xml | 8 + 19 files changed, 454 insertions(+), 131 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt create mode 100644 vector/src/main/res/drawable/ic_mail.xml create mode 100644 vector/src/main/res/drawable/ic_share_link.xml create mode 100644 vector/src/main/res/layout/bottom_sheet_space_invite.xml 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 3633bb610a..f9ffc9c612 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 @@ -77,6 +77,7 @@ import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.share.IncomingShareActivity import im.vector.app.features.signout.soft.SoftLogoutActivity +import im.vector.app.features.spaces.ShareSpaceBottomSheet import im.vector.app.features.spaces.SpaceCreationActivity import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.ui.UiStateRepository @@ -175,6 +176,7 @@ interface ScreenComponent { fun inject(bottomSheet: CallControlsBottomSheet) fun inject(bottomSheet: SignOutBottomSheetDialogFragment) fun inject(bottomSheet: MatrixToBottomSheet) + fun inject(bottomSheet: ShareSpaceBottomSheet) /* ========================================================================================== * Others diff --git a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt index c3f459f49e..f4c95215b8 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/HomeSpaceSummaryItem.kt @@ -22,6 +22,7 @@ import android.util.TypedValue import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -48,12 +49,14 @@ abstract class HomeSpaceSummaryItem : VectorEpoxyModel(R.id.groupAvatarImageView) val groupNameView by bind(R.id.groupNameView) val rootView by bind(R.id.itemGroupLayout) + val leaveView by bind(R.id.groupTmpLeave) } fun dpToPx(resources: Resources, dp: Int): Int { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 40d0cdd622..db7bb8fa16 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -111,14 +111,10 @@ class HomeActivity : val defaultRoomsId = activityResult.data?.extras?.getString(SpaceCreationActivity.RESULT_DATA_DEFAULT_ROOM_ID) views.drawerLayout.closeDrawer(GravityCompat.START) - // Here we want to change current space to the newly created one, and then immediatly open the default room + // Here we want to change current space to the newly created one, and then immediately open the default room if (spaceId != null) { - navigator.switchToSpace(this, spaceId, defaultRoomsId) + navigator.switchToSpace(this, spaceId, defaultRoomsId, true) } - - // Also we should show the share space bottomsheet - } else { - // viewModel.handle(CrossSigningSettingsAction.ReAuthCancelled) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index b7e2e189d3..a585d7609a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -91,7 +91,9 @@ import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.CurrentCallsView +import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.FailedMessagesWarningView @@ -163,6 +165,7 @@ import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData +import im.vector.app.features.spaces.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs @@ -210,7 +213,8 @@ import javax.inject.Inject data class RoomDetailArgs( val roomId: String, val eventId: String? = null, - val sharedData: SharedData? = null + val sharedData: SharedData? = null, + val openShareSpaceForId: String? = null ) : Parcelable class RoomDetailFragment @Inject constructor( @@ -293,7 +297,7 @@ class RoomDetailFragment @Inject constructor( private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var keyboardStateUtils: KeyboardStateUtils - private lateinit var callActionsHandler : StartCallActionsHandler + private lateinit var callActionsHandler: StartCallActionsHandler private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView @@ -358,9 +362,9 @@ class RoomDetailFragment @Inject constructor( } when (mode) { is SendMode.REGULAR -> renderRegularMode(mode.text) - is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) - is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) - is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + is SendMode.EDIT -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) + is SendMode.QUOTE -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.quote, mode.text) + is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) } } @@ -384,29 +388,30 @@ class RoomDetailFragment @Inject constructor( RoomDetailViewEvents.DisplayPromptForIntegrationManager -> displayPromptForIntegrationManager() is RoomDetailViewEvents.OpenStickerPicker -> openStickerPicker(it) is RoomDetailViewEvents.DisplayEnableIntegrationsWarning -> displayDisabledIntegrationDialog() - is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager() - is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it) - RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked() - is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message) - is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo) - RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() - RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() - is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) - is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it) - RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId) - RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show() - RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings() - is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item -> + is RoomDetailViewEvents.OpenIntegrationManager -> openIntegrationManager() + is RoomDetailViewEvents.OpenFile -> startOpenFileIntent(it) + RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked() + is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message) + is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo) + RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView() + RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() + is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) + is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it) + RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId) + RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show() + RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings() + is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item -> navigator.openBigImageViewer(requireActivity(), it.view, item) } - is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type) - RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() - is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) + is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type) + RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects() + is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it) }.exhaustive } if (savedInstanceState == null) { handleShareData() + handleSpaceShare() } } @@ -419,7 +424,7 @@ class RoomDetailFragment @Inject constructor( startActivity(intent) } - private fun handleChatEffect(chatEffect: ChatEffect) { + private fun handleChatEffect(chatEffect: ChatEffect) { when (chatEffect) { ChatEffect.CONFETTI -> { views.viewKonfetti.isVisible = true @@ -434,7 +439,7 @@ class RoomDetailFragment @Inject constructor( .setPosition(-50f, views.viewKonfetti.width + 50f, -50f, -50f) .streamFor(150, 3000L) } - ChatEffect.SNOW -> { + ChatEffect.SNOW -> { views.viewSnowFall.isVisible = true views.viewSnowFall.restartFalling() } @@ -627,17 +632,26 @@ class RoomDetailFragment @Inject constructor( private fun handleShareData() { when (val sharedData = roomDetailArgs.sharedData) { - is SharedData.Text -> { + is SharedData.Text -> { roomDetailViewModel.handle(RoomDetailAction.EnterRegularMode(sharedData.text, fromSharing = true)) } is SharedData.Attachments -> { // open share edition onContentAttachmentsReady(sharedData.attachmentData) } - null -> Timber.v("No share data to process") + null -> Timber.v("No share data to process") }.exhaustive } + private fun handleSpaceShare() { + roomDetailArgs.openShareSpaceForId?.let { spaceId -> + ShareSpaceBottomSheet.show(childFragmentManager, spaceId) + view?.post { + handleChatEffect(ChatEffect.CONFETTI) + } + } + } + override fun onDestroyView() { timelineEventController.callback = null timelineEventController.removeModelBuildListener(modelBuildListener) @@ -760,8 +774,8 @@ class RoomDetailFragment @Inject constructor( withState(roomDetailViewModel) { state -> // Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions val callButtonsEnabled = when (state.asyncRoomSummary.invoke()?.joinedMembersCount) { - 1 -> false - 2 -> state.isAllowedToStartWebRTCCall + 1 -> false + 2 -> state.isAllowedToStartWebRTCCall else -> state.isAllowedToManageWidgets } setOf(R.id.voice_call, R.id.video_call).forEach { @@ -791,7 +805,7 @@ class RoomDetailFragment @Inject constructor( override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.invite -> { + R.id.invite -> { navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId) true } @@ -807,19 +821,19 @@ class RoomDetailFragment @Inject constructor( callActionsHandler.onVoiceCallClicked() true } - R.id.video_call -> { + R.id.video_call -> { callActionsHandler.onVideoCallClicked() true } - R.id.hangup_call -> { + R.id.hangup_call -> { roomDetailViewModel.handle(RoomDetailAction.EndCall) true } - R.id.search -> { + R.id.search -> { handleSearchAction() true } - R.id.dev_tools -> { + R.id.dev_tools -> { navigator.openDevTools(requireContext(), roomDetailArgs.roomId) true } @@ -921,9 +935,9 @@ class RoomDetailFragment @Inject constructor( when (roomDetailPendingAction) { is RoomDetailPendingAction.JumpToReadReceipt -> roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) - is RoomDetailPendingAction.MentionUser -> + is RoomDetailPendingAction.MentionUser -> insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) - is RoomDetailPendingAction.OpenOrCreateDm -> + is RoomDetailPendingAction.OpenOrCreateDm -> roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId)) }.exhaustive } @@ -1078,9 +1092,9 @@ class RoomDetailFragment @Inject constructor( withState(roomDetailViewModel) { val showJumpToUnreadBanner = when (it.unreadState) { UnreadState.Unknown, - UnreadState.HasNoUnread -> false + UnreadState.HasNoUnread -> false is UnreadState.ReadMarkerNotLoaded -> true - is UnreadState.HasUnread -> { + is UnreadState.HasUnread -> { if (it.canShowJumpToReadMarker) { val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val positionOfReadMarker = timelineEventController.getPositionOfReadMarker() @@ -1268,7 +1282,7 @@ class RoomDetailFragment @Inject constructor( navigator.openRoom(vectorBaseActivity, async()) vectorBaseActivity.finish() } - is Fail -> { + is Fail -> { vectorBaseActivity.hideWaitingView() vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error)) } @@ -1277,19 +1291,19 @@ class RoomDetailFragment @Inject constructor( private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) { when (sendMessageResult) { - is RoomDetailViewEvents.SlashCommandHandled -> { + is RoomDetailViewEvents.SlashCommandHandled -> { sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } } - is RoomDetailViewEvents.SlashCommandError -> { + is RoomDetailViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is RoomDetailViewEvents.SlashCommandUnknown -> { + is RoomDetailViewEvents.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is RoomDetailViewEvents.SlashCommandResultOk -> { + is RoomDetailViewEvents.SlashCommandResultOk -> { updateComposerText("") } - is RoomDetailViewEvents.SlashCommandResultError -> { + is RoomDetailViewEvents.SlashCommandResultError -> { displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) } is RoomDetailViewEvents.SlashCommandNotImplemented -> { @@ -1311,7 +1325,7 @@ class RoomDetailFragment @Inject constructor( private fun displayE2eError(withHeldCode: WithHeldCode?) { val msgId = when (withHeldCode) { WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted - WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified + WithHeldCode.UNVERIFIED -> R.string.crypto_error_withheld_unverified WithHeldCode.UNAUTHORISED, WithHeldCode.UNAVAILABLE -> R.string.crypto_error_withheld_generic else -> R.string.notice_crypto_unable_to_decrypt_friendly_desc @@ -1362,7 +1376,7 @@ class RoomDetailFragment @Inject constructor( private fun displayRoomDetailActionSuccess(result: RoomDetailViewEvents.ActionSuccess) { when (val data = result.action) { - is RoomDetailAction.ReportContent -> { + is RoomDetailAction.ReportContent -> { when { data.spam -> { AlertDialog.Builder(requireActivity()) @@ -1399,7 +1413,7 @@ class RoomDetailFragment @Inject constructor( } } } - is RoomDetailAction.RequestVerification -> { + is RoomDetailAction.RequestVerification -> { Timber.v("## SAS RequestVerification action") VerificationBottomSheet.withArgs( roomDetailArgs.roomId, @@ -1414,7 +1428,7 @@ class RoomDetailFragment @Inject constructor( data.transactionId ).show(parentFragmentManager, "REQ") } - is RoomDetailAction.ResumeVerification -> { + is RoomDetailAction.ResumeVerification -> { val otherUserId = data.otherUserId ?: return VerificationBottomSheet().apply { arguments = Bundle().apply { @@ -1557,11 +1571,11 @@ class RoomDetailFragment @Inject constructor( is MessageVerificationRequestContent -> { roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) } - is MessageWithAttachmentContent -> { + is MessageWithAttachmentContent -> { val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent) roomDetailViewModel.handle(action) } - is EncryptedEventContent -> { + is EncryptedEventContent -> { roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) } } @@ -1724,75 +1738,75 @@ class RoomDetailFragment @Inject constructor( private fun handleActions(action: EventSharedAction) { when (action) { - is EventSharedAction.OpenUserProfile -> { + is EventSharedAction.OpenUserProfile -> { openRoomMemberProfile(action.userId) } - is EventSharedAction.AddReaction -> { + is EventSharedAction.AddReaction -> { emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId)) } - is EventSharedAction.ViewReactions -> { + is EventSharedAction.ViewReactions -> { ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } - is EventSharedAction.Copy -> { + is EventSharedAction.Copy -> { // I need info about the current selected message :/ copyToClipboard(requireContext(), action.content, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } - is EventSharedAction.Redact -> { + is EventSharedAction.Redact -> { promptConfirmationToRedactEvent(action) } - is EventSharedAction.Share -> { + is EventSharedAction.Share -> { onShareActionClicked(action) } - is EventSharedAction.Save -> { + is EventSharedAction.Save -> { onSaveActionClicked(action) } - is EventSharedAction.ViewEditHistory -> { + is EventSharedAction.ViewEditHistory -> { onEditedDecorationClicked(action.messageInformationData) } - is EventSharedAction.ViewSource -> { + is EventSharedAction.ViewSource -> { JSonViewerDialog.newInstance( action.content, -1, createJSonViewerStyleProvider(colorProvider) ).show(childFragmentManager, "JSON_VIEWER") } - is EventSharedAction.ViewDecryptedSource -> { + is EventSharedAction.ViewDecryptedSource -> { JSonViewerDialog.newInstance( action.content, -1, createJSonViewerStyleProvider(colorProvider) ).show(childFragmentManager, "JSON_VIEWER") } - is EventSharedAction.QuickReact -> { + is EventSharedAction.QuickReact -> { // eventId,ClickedOn,Add roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } - is EventSharedAction.Edit -> { + is EventSharedAction.Edit -> { roomDetailViewModel.handle(RoomDetailAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) } - is EventSharedAction.Quote -> { + is EventSharedAction.Quote -> { roomDetailViewModel.handle(RoomDetailAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) } - is EventSharedAction.Reply -> { + is EventSharedAction.Reply -> { roomDetailViewModel.handle(RoomDetailAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) } - is EventSharedAction.CopyPermalink -> { + is EventSharedAction.CopyPermalink -> { val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } - is EventSharedAction.Resend -> { + is EventSharedAction.Resend -> { roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId)) } - is EventSharedAction.Remove -> { + is EventSharedAction.Remove -> { roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId)) } is EventSharedAction.Cancel -> { handleCancelSend(action) } - is EventSharedAction.ReportContentSpam -> { + is EventSharedAction.ReportContentSpam -> { roomDetailViewModel.handle(RoomDetailAction.ReportContent( action.eventId, action.senderId, "This message is spam", spam = true)) } @@ -1800,22 +1814,22 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.ReportContent( action.eventId, action.senderId, "This message is inappropriate", inappropriate = true)) } - is EventSharedAction.ReportContentCustom -> { + is EventSharedAction.ReportContentCustom -> { promptReasonToReportContent(action) } - is EventSharedAction.IgnoreUser -> { + is EventSharedAction.IgnoreUser -> { action.senderId?.let { askConfirmationToIgnoreUser(it) } } - is EventSharedAction.OnUrlClicked -> { + is EventSharedAction.OnUrlClicked -> { onUrlClicked(action.url, action.title) } - is EventSharedAction.OnUrlLongClicked -> { + is EventSharedAction.OnUrlLongClicked -> { onUrlLongClicked(action.url) } - is EventSharedAction.ReRequestKey -> { + is EventSharedAction.ReRequestKey -> { roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId)) } - is EventSharedAction.UseKeyBackup -> { + is EventSharedAction.UseKeyBackup -> { context?.let { startActivity(KeysBackupRestoreActivity.intent(it)) } @@ -1955,10 +1969,10 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(requireContext(), attachmentPhotoActivityResultLauncher) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentImageActivityResultLauncher) - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment) }.exhaustive diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index d6eeade97e..80cb82098b 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -70,6 +70,7 @@ import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData +import im.vector.app.features.spaces.SpacePreviewActivity import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgsBuilder @@ -102,7 +103,7 @@ class DefaultNavigator @Inject constructor( startActivity(context, intent, buildTask) } - override fun switchToSpace(context: Context, spaceId: String, roomId: String?) { + override fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean) { if (sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId) == null) { fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast()) return @@ -115,10 +116,16 @@ class DefaultNavigator @Inject constructor( Timber.d("## Nav: Failed to switch to space $spaceId") } if (roomId != null) { - openRoom(context, roomId) + val args = RoomDetailArgs(roomId, eventId = null, openShareSpaceForId = spaceId.takeIf { openShareSheet }) + val intent = RoomDetailActivity.newIntent(context, args) + startActivity(context, intent, false) } } + override fun openSpacePreview(context: Context, spaceId: String) { + startActivity(context, SpacePreviewActivity.newIntent(context, spaceId), false) + } + override fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) { val session = sessionHolder.getSafeActiveSession() ?: return val tx = session.cryptoService().verificationService().getExistingTransaction(otherUserId, sasTransactionId) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index fcd35be162..225a2b89ab 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -38,7 +38,9 @@ interface Navigator { fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false) - fun switchToSpace(context: Context, spaceId: String, roomId: String?) + fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean) + + fun openSpacePreview(context: Context, spaceId: String) fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index ae6d630c75..d43337fab0 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.rx.rx @@ -147,33 +148,43 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti val membership = roomSummary?.membership val eventId = permalinkData.eventId val roomAlias = permalinkData.getRoomAliasOrNull() + val isSpace = roomSummary?.roomType == RoomType.SPACE return when { membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room) membership?.isActive().orFalse() -> { - navigator.openRoom(context, roomId, eventId, buildTask) + if (isSpace) { +// navigator.switchToSpace(context, roomId, null) + navigator.openSpacePreview(context, roomId) + } else { + navigator.openRoom(context, roomId, eventId, buildTask) + } } else -> { - if (roomSummary == null) { - // we don't know this room, try to peek - val roomPreviewData = RoomPreviewData( - roomId = roomId, - roomAlias = roomAlias, - peekFromServer = true, - buildTask = buildTask, - homeServers = permalinkData.viaParameters - ) - navigator.openRoomPreview(context, roomPreviewData) + if (isSpace) { + navigator.openSpacePreview(context, roomId) } else { - val roomPreviewData = RoomPreviewData( - roomId = roomId, - eventId = eventId, - roomAlias = roomAlias ?: roomSummary.canonicalAlias, - roomName = roomSummary.displayName, - avatarUrl = roomSummary.avatarUrl, - buildTask = buildTask, - homeServers = permalinkData.viaParameters - ) - navigator.openRoomPreview(context, roomPreviewData) + if (roomSummary == null) { + // we don't know this room, try to peek + val roomPreviewData = RoomPreviewData( + roomId = roomId, + roomAlias = roomAlias, + peekFromServer = true, + buildTask = buildTask, + homeServers = permalinkData.viaParameters + ) + navigator.openRoomPreview(context, roomPreviewData) + } else { + val roomPreviewData = RoomPreviewData( + roomId = roomId, + eventId = eventId, + roomAlias = roomAlias ?: roomSummary.canonicalAlias, + roomName = roomSummary.displayName, + avatarUrl = roomSummary.avatarUrl, + buildTask = buildTask, + homeServers = permalinkData.viaParameters + ) + navigator.openRoomPreview(context, roomPreviewData) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt new file mode 100644 index 0000000000..d3fb225083 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2021 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.spaces + +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import im.vector.app.R +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.core.utils.startSharePlainTextIntent +import im.vector.app.databinding.BottomSheetSpaceInviteBinding +import im.vector.app.features.invite.InviteUsersToRoomActivity +import kotlinx.parcelize.Parcelize +import javax.inject.Inject + +class ShareSpaceBottomSheet : VectorBaseBottomSheetDialogFragment() { + + @Parcelize + data class Args( + val spaceId: String + ) : Parcelable + + override val showExpanded = true + + @Inject + lateinit var activeSessionHolder: ActiveSessionHolder + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSpaceInviteBinding { + return BottomSheetSpaceInviteBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Not going for full view model for now, as it may change + + val args: Args = arguments?.getParcelable(EXTRA_ARGS) + ?: return Unit.also { dismiss() } + val summary = activeSessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(args.spaceId)?.spaceSummary() + + val spaceName = summary?.roomSummary?.name + views.descriptionText.text = getString(R.string.invite_people_to_your_space_desc, spaceName) + + views.inviteByMailButton.debouncedClicks { + } + + views.inviteByMxidButton.debouncedClicks { + val intent = InviteUsersToRoomActivity.getIntent(requireContext(), args.spaceId) + startActivity(intent) + } + + views.inviteByLinkButton.debouncedClicks { + activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createRoomPermalink(args.spaceId)?.let { permalink -> + startSharePlainTextIntent( + fragment = this, + activityResultLauncher = null, + chooserTitle = getString(R.string.share_by_text), + text = getString(R.string.share_space_link_message, spaceName, permalink), + extraTitle = getString(R.string.share_space_link_message, spaceName, permalink) + ) + } + } + + views.skipButton.debouncedClicks { + dismiss() + } + } + + companion object { + + const val EXTRA_ARGS = "EXTRA_ARGS" + + fun show(fragmentManager: FragmentManager, spaceId: String): ShareSpaceBottomSheet { + return ShareSpaceBottomSheet().apply { + isCancelable = true + arguments = Bundle().apply { + this.putParcelable(EXTRA_ARGS, ShareSpaceBottomSheet.Args(spaceId = spaceId)) + } + }.also { + it.show(fragmentManager, ShareSpaceBottomSheet::class.java.name) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index bc1af38473..f2ef3b84a0 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -80,6 +80,10 @@ class SpaceListFragment @Inject constructor( viewModel.handle(SpaceListAction.SelectSpace(spaceSummary)) } + override fun onLeaveSpace(spaceSummary: SpaceSummary) { + viewModel.handle(SpaceListAction.LeaveSpace(spaceSummary)) + } + override fun onAddSpaceSelected() { viewModel.handle(SpaceListAction.AddSpace) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index d1bc3c6e1c..0402beb428 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -71,6 +71,7 @@ class SpaceSummaryController @Inject constructor( matrixItem(it.toMatrixItem()) selected(false) listener { callback?.onSpaceSelected(it) } +// lea { callback?.onSpaceSelected(it) } } } genericFooterItem { @@ -101,6 +102,7 @@ class SpaceSummaryController @Inject constructor( id(groupSummary.spaceId) matrixItem(groupSummary.toMatrixItem()) selected(isSelected) + onLeave { callback?.onLeaveSpace(groupSummary) } listener { callback?.onSpaceSelected(groupSummary) } } } @@ -118,6 +120,7 @@ class SpaceSummaryController @Inject constructor( interface Callback { fun onSpaceSelected(spaceSummary: SpaceSummary) + fun onLeaveSpace(spaceSummary: SpaceSummary) fun onAddSpaceSelected() } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index bf3a47461f..3f1971d9dc 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -18,12 +18,14 @@ package im.vector.app.features.spaces import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isVisible 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.platform.CheckableConstraintLayout +import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.util.MatrixItem @@ -34,12 +36,22 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var listener: (() -> Unit)? = null + @EpoxyAttribute var onLeave: (() -> Unit)? = null override fun bind(holder: Holder) { super.bind(holder) holder.rootView.setOnClickListener { listener?.invoke() } holder.groupNameView.text = matrixItem.displayName holder.rootView.isChecked = selected + if (onLeave != null) { + holder.leaveView.setOnClickListener( + DebouncedClickListener({ _ -> + onLeave?.invoke() + }) + ) + } else { + holder.leaveView.isVisible = false + } avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) } @@ -52,5 +64,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { val avatarImageView by bind(R.id.groupAvatarImageView) val groupNameView by bind(R.id.groupNameView) val rootView by bind(R.id.itemGroupLayout) + val leaveView by bind(R.id.groupTmpLeave) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 4ec6e7d4ad..28bc358e36 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.spaces +import androidx.lifecycle.viewModelScope import arrow.core.Option import com.airbnb.mvrx.Async import com.airbnb.mvrx.FragmentViewModelContext @@ -34,18 +35,22 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.grouplist.SelectedSpaceDataSource import io.reactivex.Observable import io.reactivex.functions.BiFunction +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" sealed class SpaceListAction : VectorViewModelAction { data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction() + data class LeaveSpace(val spaceSummary: SpaceSummary) : SpaceListAction() object AddSpace : SpaceListAction() } @@ -88,13 +93,18 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp init { observeSpaceSummaries() observeSelectionState() - selectedSpaceDataSource.observe().execute { - if (this.selectedSpace != it.invoke()?.orNull()) { - copy( - selectedSpace = it.invoke()?.orNull() - ) - } else this - } + selectedSpaceDataSource + .observe() + .subscribe { + if (currentGroupId != it.orNull()?.spaceId) { + setState { + copy( + selectedSpace = it.orNull() + ) + } + } + } + .disposeOnClear() } private fun observeSelectionState() { @@ -119,11 +129,12 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp override fun handle(action: SpaceListAction) { when (action) { is SpaceListAction.SelectSpace -> handleSelectSpace(action) - else -> handleAddSpace() + is SpaceListAction.LeaveSpace -> handleLeaveSpace(action) + SpaceListAction.AddSpace -> handleAddSpace() } } - // PRIVATE METHODS ***************************************************************************** +// PRIVATE METHODS ***************************************************************************** private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> // get uptodate version of the space @@ -146,6 +157,16 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } } + private fun handleLeaveSpace(action: SpaceListAction.LeaveSpace) { + viewModelScope.launch { + awaitCallback { + tryOrNull("Failed to leave space ${action.spaceSummary.spaceId}") { + session.spaceService().getSpace(action.spaceSummary.spaceId)?.asRoom()?.leave(null, it) + } + } + } + } + private fun handleAddSpace() { _viewEvents.post(SpaceListViewEvents.AddSpace) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt index 066c329c34..a6b9b1978a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/WizardButtonView.kt @@ -17,6 +17,7 @@ package im.vector.app.features.spaces.create import android.content.Context +import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.os.Build import android.util.AttributeSet @@ -25,6 +26,7 @@ import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.withStyledAttributes import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide import im.vector.app.databinding.ViewSpaceTypeButtonBinding class WizardButtonView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) @@ -36,7 +38,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib set(value) { if (value != title) { field = value - views.title.text = value + views.title.setTextOrHide(value) } } @@ -44,7 +46,7 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib set(value) { if (value != subTitle) { field = value - views.subTitle.text = value + views.subTitle.setTextOrHide(value) } } @@ -56,15 +58,13 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib } } -// private var tint: Int? = null -// set(value) { -// field = value -// if (value != null) { -// views.buttonImageView.imageTintList = ColorStateList.valueOf(value) -// } else { -// views.buttonImageView.clearColorFilter() -// } -// } + private var tint: Int? = null + set(value) { + field = value + if (value != null) { + views.buttonImageView.imageTintList = ColorStateList.valueOf(value) + } + } // var action: (() -> Unit)? = null @@ -72,16 +72,19 @@ class WizardButtonView @JvmOverloads constructor(context: Context, attrs: Attrib inflate(context, R.layout.view_space_type_button, this) views = ViewSpaceTypeButtonBinding.bind(this) + views.subTitle.setTextOrHide(null) + if (isInEditMode) { title = "Title" subTitle = "This is doing something" } context.withStyledAttributes(attrs, R.styleable.WizardButtonView) { - title = getString(R.styleable.WizardButtonView_title) ?: "" - subTitle = getString(R.styleable.WizardButtonView_subTitle) ?: "" + title = getString(R.styleable.WizardButtonView_title) + subTitle = getString(R.styleable.WizardButtonView_subTitle) icon = getDrawable(R.styleable.WizardButtonView_icon) -// tint = getColor(R.styleable.WizardButtonView_iconTint, ThemeUtils.getColor(context, R.attr.riotx_text_primary)) + tint = getColor(R.styleable.WizardButtonView_iconTint, -1) + .takeIf { it != -1 } } val outValue = TypedValue() diff --git a/vector/src/main/res/drawable/ic_mail.xml b/vector/src/main/res/drawable/ic_mail.xml new file mode 100644 index 0000000000..80d25166b0 --- /dev/null +++ b/vector/src/main/res/drawable/ic_mail.xml @@ -0,0 +1,21 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_share_link.xml b/vector/src/main/res/drawable/ic_share_link.xml new file mode 100644 index 0000000000..d4eceb3b9a --- /dev/null +++ b/vector/src/main/res/drawable/ic_share_link.xml @@ -0,0 +1,12 @@ + + + diff --git a/vector/src/main/res/layout/bottom_sheet_space_invite.xml b/vector/src/main/res/layout/bottom_sheet_space_invite.xml new file mode 100644 index 0000000000..c8d29b2f7d --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_space_invite.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_space.xml b/vector/src/main/res/layout/item_space.xml index 9cd07f3215..f52ef31ead 100644 --- a/vector/src/main/res/layout/item_space.xml +++ b/vector/src/main/res/layout/item_space.xml @@ -35,11 +35,26 @@ android:textSize="15sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator" - app:layout_constraintEnd_toStartOf="@+id/groupAvatarChevron" + app:layout_constraintEnd_toStartOf="@+id/groupTmpLeave" app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/lorem/random" /> + + @@ -53,7 +54,8 @@ app:layout_constraintStart_toEndOf="@id/buttonImageView" app:layout_constraintTop_toBottomOf="@id/title" app:layout_goneMarginBottom="8dp" - tools:text="Open to anyone, best for communities" /> + tools:text="Open to anyone, best for communities" + tools:visibility="visible" /> We’ll create rooms for them, and auto-join everyone. You can add more later too. General Creating Space… + Invite people to your space + It’s just you at the moment. %s will be even better with others. + Invite by email + Invite by username + Share link + + Join my space %1$s %2$s + Skip for now From 2952dca3a35e2309f2f10ad171e9cd25f4d0ce35 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 25 Feb 2021 18:50:49 +0100 Subject: [PATCH 112/230] Default Rooms Handling --- .../java/im/vector/app/features/form/FormEditTextItem.kt | 7 +++++++ .../app/features/spaces/create/CreateSpaceViewModel.kt | 5 ++++- .../spaces/create/SpaceDefaultRoomEpoxyController.kt | 4 ++++ vector/src/main/res/values/strings.xml | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) 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 a1bd2bd1a3..42ce6d68a8 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 @@ -57,6 +57,9 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var imeOptions: Int? = null + @EpoxyAttribute + var endIconMode: Int? = null + @EpoxyAttribute var onTextChange: ((String) -> Unit)? = null @@ -72,6 +75,10 @@ abstract class FormEditTextItem : VectorEpoxyModel() { holder.textInputLayout.hint = hint holder.textInputLayout.error = errorMessage + endIconMode?.let { mode -> + holder.textInputLayout.endIconMode = mode + } + // Update only if text is different and value is not null holder.textInputEditText.setTextSafe(value) holder.textInputEditText.isEnabled = enabled diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index cd2680ef5f..9988bbe003 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -111,7 +111,10 @@ class CreateSpaceViewModel @AssistedInject constructor( override fun initialState(viewModelContext: ViewModelContext): CreateSpaceState? { return CreateSpaceState( - defaultRooms = mapOf(0 to viewModelContext.activity.getString(R.string.create_spaces_default_public_room_name)) + defaultRooms = mapOf( + 0 to viewModelContext.activity.getString(R.string.create_spaces_default_public_room_name), + 1 to viewModelContext.activity.getString(R.string.create_spaces_default_public_random_room_name) + ) ) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt index 27b57713ba..800de01b62 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt @@ -17,6 +17,7 @@ package im.vector.app.features.spaces.create import com.airbnb.epoxy.TypedEpoxyController +import com.google.android.material.textfield.TextInputLayout import im.vector.app.R import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -51,6 +52,7 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( enabled(true) value(data?.defaultRooms?.get(0)) hint(stringProvider.getString(R.string.create_room_name_section)) + endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) showBottomSeparator(false) onTextChange { text -> listener?.onNameChange(0, text) @@ -62,6 +64,7 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( enabled(true) value(data?.defaultRooms?.get(1)) hint(stringProvider.getString(R.string.create_room_name_section)) + endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) showBottomSeparator(false) onTextChange { text -> listener?.onNameChange(1, text) @@ -73,6 +76,7 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( enabled(true) value(data?.defaultRooms?.get(2)) hint(stringProvider.getString(R.string.create_room_name_section)) + endIconMode(TextInputLayout.END_ICON_CLEAR_TEXT) showBottomSeparator(false) onTextChange { text -> listener?.onNameChange(2, text) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 41b489002b..5c6966ef4e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3276,6 +3276,7 @@ What are some discussions you want to have in Runner’s World? We’ll create rooms for them, and auto-join everyone. You can add more later too. General + Random Creating Space… Invite people to your space It’s just you at the moment. %s will be even better with others. From a433f2f965355a0d06da06380f6a8efa34897433 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 26 Feb 2021 09:14:44 +0100 Subject: [PATCH 113/230] a11y --- vector/src/main/res/layout/item_editable_square_avatar.xml | 1 + vector/src/main/res/layout/item_space.xml | 2 +- vector/src/main/res/layout/item_space_subspace.xml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_editable_square_avatar.xml b/vector/src/main/res/layout/item_editable_square_avatar.xml index b3ec057fd4..3112b36a65 100644 --- a/vector/src/main/res/layout/item_editable_square_avatar.xml +++ b/vector/src/main/res/layout/item_editable_square_avatar.xml @@ -57,6 +57,7 @@ android:backgroundTint="?riotx_background" android:padding="8dp" android:src="@drawable/ic_camera_plain" + android:contentDescription="@string/a11y_change_avatar" app:tint="?riotx_text_secondary" app:layout_constraintCircle="@+id/itemEditableAvatarImageContainer" app:layout_constraintCircleAngle="135" diff --git a/vector/src/main/res/layout/item_space.xml b/vector/src/main/res/layout/item_space.xml index f52ef31ead..25b685b999 100644 --- a/vector/src/main/res/layout/item_space.xml +++ b/vector/src/main/res/layout/item_space.xml @@ -50,7 +50,7 @@ android:layout_marginEnd="4dp" android:importantForAccessibility="no" android:src="@drawable/ic_room_actions_leave" - android:tint="@color/riotx_destructive_accent" + app:tint="@color/riotx_destructive_accent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/groupAvatarChevron" app:layout_constraintTop_toTopOf="parent" /> diff --git a/vector/src/main/res/layout/item_space_subspace.xml b/vector/src/main/res/layout/item_space_subspace.xml index ac654dc2b3..d3b243132e 100644 --- a/vector/src/main/res/layout/item_space_subspace.xml +++ b/vector/src/main/res/layout/item_space_subspace.xml @@ -28,6 +28,7 @@ android:layout_height="24dp" android:scaleType="centerInside" android:visibility="visible" + android:contentDescription="@string/a11y_image" android:layout_marginStart="8dp" app:layout_constraintBottom_toBottomOf="@id/childSpaceName" app:layout_constraintStart_toEndOf="@id/childSpaceTab" From 03ef480bea14a1c958655e0d71093eebcd88b1f2 Mon Sep 17 00:00:00 2001 From: Valere Date: Sat, 27 Feb 2021 11:17:19 +0100 Subject: [PATCH 114/230] Fix missing param in string --- .../features/spaces/create/SpaceDefaultRoomEpoxyController.kt | 2 +- vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt index 800de01b62..a1e46457e3 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDefaultRoomEpoxyController.kt @@ -37,7 +37,7 @@ class SpaceDefaultRoomEpoxyController @Inject constructor( genericFooterItem { id("info_help_header") style(GenericItem.STYLE.BIG_TEXT) - text(stringProvider.getString(R.string.create_spaces_room_public_header)) + text(stringProvider.getString(R.string.create_spaces_room_public_header, data?.name)) textColor(colorProvider.getColorFromAttribute(R.attr.riot_primary_text_color)) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 5c6966ef4e..272bec27fe 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3273,7 +3273,7 @@ Add some details to help it stand out. You can change these at any point. Add some details to help people identify it. You can change these at any point. Give it a name to continue. - What are some discussions you want to have in Runner’s World? + What are some discussions you want to have in %s? We’ll create rooms for them, and auto-join everyone. You can add more later too. General Random From ef42591534effb92d45c7e374d309d3e99a46a5d Mon Sep 17 00:00:00 2001 From: Valere Date: Sat, 27 Feb 2021 11:13:54 +0100 Subject: [PATCH 115/230] Open Space Link initial commit --- .../api/session/room/peeking/PeekResult.kt | 1 + .../session/room/peeking/PeekRoomTask.kt | 9 +- .../im/vector/app/core/di/FragmentModule.kt | 12 + .../vector/app/features/home/HomeActivity.kt | 17 ++ .../home/room/detail/RoomDetailFragment.kt | 4 +- .../app/features/matrixto/MatrixToAction.kt | 7 + .../features/matrixto/MatrixToBottomSheet.kt | 83 +++---- .../matrixto/MatrixToBottomSheetState.kt | 34 ++- .../matrixto/MatrixToBottomSheetViewModel.kt | 197 +++++++++++++++-- .../matrixto/MatrixToRoomSpaceFragment.kt | 207 ++++++++++++++++++ .../features/matrixto/MatrixToUserFragment.kt | 99 +++++++++ .../features/matrixto/MatrixToViewEvents.kt | 2 + .../features/permalink/PermalinkHandler.kt | 10 +- .../roomdirectory/PublicRoomsFragment.kt | 3 +- .../layout/bottom_sheet_matrix_to_card.xml | 88 +------- .../fragment_matrix_to_room_space_card.xml | 145 ++++++++++++ .../layout/fragment_matrix_to_user_card.xml | 86 ++++++++ vector/src/main/res/values/strings.xml | 3 + 18 files changed, 851 insertions(+), 156 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt create mode 100644 vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml create mode 100644 vector/src/main/res/layout/fragment_matrix_to_user_card.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt index a27e88aced..c213eee08d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt @@ -24,6 +24,7 @@ sealed class PeekResult { val topic: String?, val avatarUrl: String?, val numJoinedMembers: Int?, + val roomType: String?, val viaServers: List ) : PeekResult() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt index 5b211c505f..5d45259304 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult @@ -100,7 +101,8 @@ internal class DefaultPeekRoomTask @Inject constructor( name = publicRepoResult.name, topic = publicRepoResult.topic, numJoinedMembers = publicRepoResult.numJoinedMembers, - viaServers = serverList + viaServers = serverList, + roomType = null // would be nice to get that from directory... ) } @@ -130,6 +132,10 @@ internal class DefaultPeekRoomTask @Inject constructor( .distinctBy { it.stateKey } .count() + val roomType = stateEvents + .lastOrNull { it.type == EventType.STATE_ROOM_CREATE } + ?.let { it.content?.toModel()?.type } + return PeekResult.Success( roomId = roomId, alias = alias, @@ -137,6 +143,7 @@ internal class DefaultPeekRoomTask @Inject constructor( name = name, topic = topic, numJoinedMembers = memberCount, + roomType = roomType, viaServers = serverList ) } catch (failure: Throwable) { 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 d3b46b5c64..a1be337dc6 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 @@ -72,6 +72,8 @@ import im.vector.app.features.login.LoginSplashFragment import im.vector.app.features.login.LoginWaitForEmailFragment import im.vector.app.features.login.LoginWebFragment import im.vector.app.features.login.terms.LoginTermsFragment +import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment +import im.vector.app.features.matrixto.MatrixToUserFragment import im.vector.app.features.pin.PinFragment import im.vector.app.features.qrcode.QrCodeScannerFragment import im.vector.app.features.reactions.EmojiChooserFragment @@ -654,4 +656,14 @@ interface FragmentModule { @IntoMap @FragmentKey(CreateSpaceDefaultRoomsFragment::class) fun bindCreateSpaceDefaultRoomsFragment(fragment: CreateSpaceDefaultRoomsFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(MatrixToUserFragment::class) + fun bindMatrixToUserFragment(fragment: MatrixToUserFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(MatrixToRoomSpaceFragment::class) + fun bindMatrixToRoomSpaceFragment(fragment: MatrixToRoomSpaceFragment): Fragment } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index db7bb8fa16..84098b8281 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -451,6 +451,23 @@ class HomeActivity : return true } + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + if (roomId == null) return false + val listener = object : MatrixToBottomSheet.InteractionListener { + override fun navigateToRoom(roomId: String) { + navigator.openRoom(this@HomeActivity, roomId) + } + + override fun switchToSpace(spaceId: String) { + navigator.switchToSpace(this@HomeActivity, spaceId, null, false) + } + } + + MatrixToBottomSheet.withLink(deepLink.toString(), listener) + .show(supportFragmentManager, "HA#MatrixToBottomSheet") + return true + } + companion object { fun newIntent(context: Context, clearNotification: Boolean = false, accountCreation: Boolean = false): Intent { val args = HomeActivityArgs( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index a585d7609a..4d7f9b64f0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1445,7 +1445,7 @@ class RoomDetailFragment @Inject constructor( override fun onUrlClicked(url: String, title: String): Boolean { permalinkHandler .launch(requireActivity(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { // Same room? if (roomId == roomDetailArgs.roomId) { // Navigation to same room @@ -1653,7 +1653,7 @@ class RoomDetailFragment @Inject constructor( override fun onRoomCreateLinkClicked(url: String) { permalinkHandler .launch(requireContext(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { requireActivity().finish() return false } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt index e1c6800494..1c9d0bbb18 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt @@ -21,4 +21,11 @@ import org.matrix.android.sdk.api.util.MatrixItem sealed class MatrixToAction : VectorViewModelAction { data class StartChattingWithUser(val matrixItem: MatrixItem) : MatrixToAction() + object FailedToResolveUser : MatrixToAction() + object FailedToStartChatting : MatrixToAction() + data class JoinSpace(val spaceID: String, val viaServers: List?) : MatrixToAction() + data class JoinRoom(val roomId: String, val viaServers: List?) : MatrixToAction() + data class OpenSpace(val spaceID: String) : MatrixToAction() + data class OpenRoom(val roomId: String) : MatrixToAction() +// data class OpenSpace(val spaceID: String) : MatrixToAction() } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt index 1d897477a2..b9ef833850 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -21,22 +21,23 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.view.isInvisible +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading +import androidx.fragment.app.Fragment +import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.MvRx -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized 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.setTextOrHide +import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetMatrixToCardBinding import im.vector.app.features.home.AvatarRenderer import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.permalinks.PermalinkData import javax.inject.Inject +import kotlin.reflect.KClass class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { @@ -65,63 +66,41 @@ class MatrixToBottomSheet : interface InteractionListener { fun navigateToRoom(roomId: String) + fun switchToSpace(spaceId: String) {} } override fun invalidate() = withState(viewModel) { state -> super.invalidate() - when (val item = state.matrixItem) { - Uninitialized -> { - views.matrixToCardContentLoading.isVisible = false - views.matrixToCardUserContentVisibility.isVisible = false + when (state.linkType) { + is PermalinkData.RoomLink -> { + views.matrixToCardContentLoading.isVisible = state.roomPeekResult is Incomplete + showFragment(MatrixToRoomSpaceFragment::class, Bundle()) } - is Loading -> { - views.matrixToCardContentLoading.isVisible = true - views.matrixToCardUserContentVisibility.isVisible = false + is PermalinkData.UserLink -> { + views.matrixToCardContentLoading.isVisible = state.matrixItem is Incomplete + showFragment(MatrixToUserFragment::class, Bundle()) } - is Success -> { - views.matrixToCardContentLoading.isVisible = false - views.matrixToCardUserContentVisibility.isVisible = true - views.matrixToCardNameText.setTextOrHide(item.invoke().displayName) - views.matrixToCardUserIdText.setTextOrHide(item.invoke().id) - avatarRenderer.render(item.invoke(), views.matrixToCardAvatar) + is PermalinkData.GroupLink -> { } - is Fail -> { - // TODO display some error copy? - dismiss() + is PermalinkData.FallbackLink -> { } } + } - when (state.startChattingState) { - Uninitialized -> { - views.matrixToCardButtonLoading.isVisible = false - views.matrixToCardSendMessageButton.isVisible = false - } - is Success -> { - views.matrixToCardButtonLoading.isVisible = false - views.matrixToCardSendMessageButton.isVisible = true - } - is Fail -> { - views.matrixToCardButtonLoading.isVisible = false - views.matrixToCardSendMessageButton.isVisible = true - // TODO display some error copy? - dismiss() - } - is Loading -> { - views.matrixToCardButtonLoading.isVisible = true - views.matrixToCardSendMessageButton.isInvisible = true + private fun showFragment(fragmentClass: KClass, bundle: Bundle) { + if (childFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { + childFragmentManager.commitTransaction { + replace(views.matrixToCardFragmentContainer.id, + fragmentClass.java, + bundle, + fragmentClass.simpleName + ) } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - views.matrixToCardSendMessageButton.debouncedClicks { - withState(viewModel) { - it.matrixItem.invoke()?.let { item -> - viewModel.handle(MatrixToAction.StartChattingWithUser(item)) - } - } - } viewModel.observeViewEvents { when (it) { @@ -130,6 +109,16 @@ class MatrixToBottomSheet : dismiss() } MatrixToViewEvents.Dismiss -> dismiss() + is MatrixToViewEvents.NavigateToSpace -> { + interactionListener?.switchToSpace(it.spaceId) + dismiss() + } + is MatrixToViewEvents.ShowModalError -> { + AlertDialog.Builder(requireContext()) + .setMessage(it.error) + .setPositiveButton(getString(R.string.ok), null) + .show() + } } } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt index 9b1ce9fea8..c0d42d60dd 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -19,15 +19,45 @@ package im.vector.app.features.matrixto import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.api.session.permalinks.PermalinkData +import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.util.MatrixItem data class MatrixToBottomSheetState( val deepLink: String, + val linkType: PermalinkData, val matrixItem: Async = Uninitialized, - val startChattingState: Async = Uninitialized + val startChattingState: Async = Uninitialized, + val roomPeekResult: Async = Uninitialized ) : MvRxState { constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( - deepLink = args.matrixToLink + deepLink = args.matrixToLink, + linkType = PermalinkParser.parse(args.matrixToLink) ) } + +sealed class RoomInfoResult { + data class FullInfo( + val roomItem: MatrixItem.RoomItem, + val name: String, + val topic: String, + val memberCount: Int?, + val alias: String?, + val membership: Membership, + val roomType: String?, + val viaServers: List? + ) : RoomInfoResult() + + data class PartialInfo( + val roomId: String?, + val viaServers: List + ) : RoomInfoResult() + + data class UnknownAlias( + val alias: String? + ) : RoomInfoResult() + + object NotFound : RoomInfoResult() +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index b961383575..267322b58a 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -25,9 +25,10 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -35,19 +36,26 @@ import im.vector.app.features.createdirect.DirectRoomHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.permalinks.PermalinkData -import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription +import org.matrix.android.sdk.internal.util.awaitCallback +import timber.log.Timber class MatrixToBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: MatrixToBottomSheetState, private val session: Session, private val stringProvider: StringProvider, private val directRoomHelper: DirectRoomHelper, - private val rawService: RawService) : VectorViewModel(initialState) { + private val errorFormatter: ErrorFormatter) + : VectorViewModel(initialState) { @AssistedFactory interface Factory { @@ -55,8 +63,23 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } init { - setState { - copy(matrixItem = Loading()) + when (initialState.linkType) { + is PermalinkData.RoomLink -> { + setState { + copy(roomPeekResult = Loading()) + } + } + is PermalinkData.UserLink -> { + setState { + copy(matrixItem = Loading()) + } + } + is PermalinkData.GroupLink -> { + // Not yet supported + } + is PermalinkData.FallbackLink -> { + // Not yet supported + } } viewModelScope.launch(Dispatchers.IO) { resolveLink(initialState) @@ -64,7 +87,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveLink(initialState: MatrixToBottomSheetState) { - val permalinkData = PermalinkParser.parse(initialState.deepLink) + val permalinkData = initialState.linkType if (permalinkData is PermalinkData.FallbackLink) { setState { copy( @@ -75,8 +98,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( return } - when (permalinkData) { - is PermalinkData.UserLink -> { + when (permalinkData) { + is PermalinkData.UserLink -> { val user = resolveUser(permalinkData.userId) setState { copy( @@ -85,11 +108,79 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - is PermalinkData.RoomLink -> { - // not yet supported - _viewEvents.post(MatrixToViewEvents.Dismiss) + is PermalinkData.RoomLink -> { + // could this room be already known + val knownRoom = if (permalinkData.isRoomAlias) { + tryOrNull { + awaitCallback> { + session.getRoomIdByAlias(permalinkData.roomIdOrAlias, false, it) + } + } + ?.getOrNull() + ?.roomId?.let { + session.getRoom(permalinkData.roomIdOrAlias) + } + } else { + session.getRoom(permalinkData.roomIdOrAlias) + }?.roomSummary() + + if (knownRoom != null) { + setState { + copy( + roomPeekResult = Success( + RoomInfoResult.FullInfo( + roomItem = knownRoom.toMatrixItem(), + name = knownRoom.name, + topic = knownRoom.topic, + memberCount = knownRoom.joinedMembersCount, + alias = knownRoom.canonicalAlias, + membership = knownRoom.membership, + roomType = knownRoom.roomType, + viaServers = null + ) + ) + ) + } + } else { + + val result = when (val peekResult = tryOrNull { resolveRoom(permalinkData.roomIdOrAlias) }) { + is PeekResult.Success -> { + RoomInfoResult.FullInfo( + roomItem = MatrixItem.RoomItem(peekResult.roomId, peekResult.name, peekResult.avatarUrl), + name = peekResult.name ?: "", + topic = peekResult.topic ?: "", + memberCount = peekResult.numJoinedMembers, + alias = peekResult.alias, + membership = Membership.NONE, + roomType = peekResult.roomType, + viaServers = peekResult.viaServers.takeIf { it.isNotEmpty() } ?: permalinkData.viaParameters + ) + } + is PeekResult.PeekingNotAllowed -> { + RoomInfoResult.PartialInfo( + roomId = permalinkData.roomIdOrAlias, + viaServers = permalinkData.viaParameters + ) + } + PeekResult.UnknownAlias -> { + RoomInfoResult.NotFound + } + null -> { + RoomInfoResult.PartialInfo( + roomId = permalinkData.roomIdOrAlias, + viaServers = permalinkData.viaParameters + ).takeIf { permalinkData.isRoomAlias.not() } + ?: RoomInfoResult.NotFound + } + } + setState { + copy( + roomPeekResult = Success(result) + ) + } + } } - is PermalinkData.GroupLink -> { + is PermalinkData.GroupLink -> { // not yet supported _viewEvents.post(MatrixToViewEvents.Dismiss) } @@ -105,6 +196,19 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ?: User(userId, null, null) } + /** + * Let's try to get some information about that room, + * main thing is trying to see if it's a space or a room + */ + private suspend fun resolveRoom(roomIdOrAlias: String): PeekResult { + return tryOrNull { // this should not throw as it returns a result, but better be safe + awaitCallback { + session.peekRoom(roomIdOrAlias, it) + } + } + ?: PeekResult.PeekingNotAllowed(roomIdOrAlias, null, emptyList()) + } + companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? { val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() @@ -116,14 +220,75 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( override fun handle(action: MatrixToAction) { when (action) { is MatrixToAction.StartChattingWithUser -> handleStartChatting(action) + MatrixToAction.FailedToResolveUser -> { + _viewEvents.post(MatrixToViewEvents.Dismiss) + } + MatrixToAction.FailedToStartChatting -> { + _viewEvents.post(MatrixToViewEvents.Dismiss) + } + is MatrixToAction.JoinSpace -> handleJoinSpace(action) + is MatrixToAction.JoinRoom -> handleJoinRoom(action) + is MatrixToAction.OpenSpace -> { + _viewEvents.post(MatrixToViewEvents.NavigateToSpace(action.spaceID)) + } + is MatrixToAction.OpenRoom -> { + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId)) + } }.exhaustive } - private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) { + private fun handleJoinSpace(joinSpace: MatrixToAction.JoinSpace) { + setState { + copy(startChattingState = Loading()) + } viewModelScope.launch { - setState { - copy(startChattingState = Loading()) + try { + val joinResult = session.spaceService().joinSpace(joinSpace.spaceID, null, joinSpace.viaServers?.take(3) ?: emptyList()) + if (joinResult.isSuccess()) { + _viewEvents.post(MatrixToViewEvents.NavigateToSpace(joinSpace.spaceID)) + } else { + val errMsg = errorFormatter.toHumanReadable((joinResult as? SpaceService.JoinSpaceResult.Fail)?.error) + _viewEvents.post(MatrixToViewEvents.ShowModalError(errMsg)) + } + } catch (failure: Throwable) { + _viewEvents.post(MatrixToViewEvents.ShowModalError(errorFormatter.toHumanReadable(failure))) + } finally { + setState { + // we can hide this button has we will navigate out + copy(startChattingState = Uninitialized) + } } + + } + } + + private fun handleJoinRoom(action: MatrixToAction.JoinRoom) { + setState { + copy(startChattingState = Loading()) + } + viewModelScope.launch { + try { + awaitCallback { + session.joinRoom(action.roomId, null, action.viaServers?.take(3) ?: emptyList(), it) + } + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(action.roomId)) + } catch (failure: Throwable) { + _viewEvents.post(MatrixToViewEvents.ShowModalError(errorFormatter.toHumanReadable(failure))) + } finally { + setState { + // we can hide this button has we will navigate out + copy(startChattingState = Uninitialized) + } + } + + } + } + + private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) { + setState { + copy(startChattingState = Loading()) + } + viewModelScope.launch { val roomId = try { directRoomHelper.ensureDMExists(action.matrixItem.id) } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt new file mode 100644 index 0000000000..8ab2a46bad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2021 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.matrixto + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentMatrixToRoomSpaceCardBinding +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomType +import javax.inject.Inject + +class MatrixToRoomSpaceFragment @Inject constructor( + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment() { + + private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentMatrixToRoomSpaceCardBinding { + return FragmentMatrixToRoomSpaceCardBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + views.matrixToCardMainButton.debouncedClicks { + mainButtonClicked() + } + views.matrixToCardSecondaryButton.debouncedClicks { + secondaryButtonClicked() + } + } + + override fun invalidate() = withState(sharedViewModel) { state -> + when (val item = state.roomPeekResult) { + Uninitialized -> { + views.matrixToCardContentVisibility.isVisible = false + } + is Loading -> { + views.matrixToCardContentVisibility.isVisible = false + } + is Success -> { + views.matrixToCardContentVisibility.isVisible = true + when (val peek = item.invoke()) { + is RoomInfoResult.FullInfo -> { + val matrixItem = peek.roomItem + if (peek.roomType == RoomType.SPACE) { + avatarRenderer.renderSpace(matrixItem, views.matrixToCardAvatar) + } else { + avatarRenderer.render(matrixItem, views.matrixToCardAvatar) + } + views.matrixToCardNameText.setTextOrHide(peek.name) + views.matrixToCardAliasText.setTextOrHide(peek.alias) + views.matrixToCardDescText.setTextOrHide(peek.topic) + val memberCount = peek.memberCount + if (memberCount != null) { + views.matrixToMemberPills.isVisible = true + views.spaceChildMemberCountText.text = resources.getQuantityString(R.plurals.room_title_members, memberCount, memberCount) + } else { + // hide the pill + views.matrixToMemberPills.isVisible = false + } + + when (peek.membership) { + Membership.LEAVE, + Membership.NONE -> { + views.matrixToCardMainButton.isVisible = true + views.matrixToCardMainButton.text = getString(R.string.join_space) + views.matrixToCardSecondaryButton.isVisible = false + } + Membership.INVITE -> { + views.matrixToCardMainButton.isVisible = true + views.matrixToCardSecondaryButton.isVisible = true + views.matrixToCardMainButton.text = getString(R.string.join_space) + views.matrixToCardSecondaryButton.text = getString(R.string.decline) + } + Membership.JOIN -> { + views.matrixToCardMainButton.isVisible = true + views.matrixToCardSecondaryButton.isVisible = false + views.matrixToCardMainButton.text = getString(R.string.action_open) + } + Membership.KNOCK, + Membership.BAN -> { + // What to do here ? + views.matrixToCardMainButton.isVisible = false + views.matrixToCardSecondaryButton.isVisible = false + } + } + } + is RoomInfoResult.PartialInfo -> { + // It may still be possible to join + views.matrixToCardNameText.text = peek.roomId + views.matrixToCardAliasText.isVisible = false + views.matrixToMemberPills.isVisible = false + views.matrixToCardDescText.setTextOrHide(getString(R.string.room_preview_no_preview)) + + views.matrixToCardMainButton.text = getString(R.string.join_anyway) + views.matrixToCardSecondaryButton.isVisible = false + } + RoomInfoResult.NotFound -> { + // we cannot join :/ + views.matrixToCardNameText.isVisible = false + views.matrixToCardAliasText.isVisible = false + views.matrixToMemberPills.isVisible = false + views.matrixToCardDescText.setTextOrHide(getString(R.string.room_preview_not_found)) + + views.matrixToCardMainButton.isVisible = false + views.matrixToCardSecondaryButton.isVisible = false + } + is RoomInfoResult.UnknownAlias -> { + views.matrixToCardNameText.isVisible = false + views.matrixToCardAliasText.isVisible = false + views.spaceChildMemberCountText.isVisible = false + views.matrixToCardDescText.setTextOrHide(getString(R.string.room_alias_preview_not_found)) + + views.matrixToCardMainButton.isVisible = false + views.matrixToCardSecondaryButton.isVisible = false + } + } + } + is Fail -> { + // TODO display some error copy? + sharedViewModel.handle(MatrixToAction.FailedToResolveUser) + } + } + + when (state.startChattingState) { + Uninitialized -> { + views.matrixToCardButtonLoading.isVisible = false +// views.matrixToCardMainButton.isVisible = false + } + is Success -> { + views.matrixToCardButtonLoading.isVisible = false + views.matrixToCardMainButton.isVisible = true + } + is Fail -> { + views.matrixToCardButtonLoading.isVisible = false + views.matrixToCardMainButton.isVisible = true + // TODO display some error copy? + } + is Loading -> { + views.matrixToCardButtonLoading.isVisible = true + views.matrixToCardMainButton.isInvisible = true + } + } + } + + private fun mainButtonClicked() = withState(sharedViewModel) { state -> + when (val info = state.roomPeekResult.invoke()) { + is RoomInfoResult.FullInfo -> { + when (info.membership) { + Membership.NONE, + Membership.INVITE, + Membership.LEAVE -> { + if (info.roomType == RoomType.SPACE) { + sharedViewModel.handle(MatrixToAction.JoinSpace(info.roomItem.id, info.viaServers)) + } else { + sharedViewModel.handle(MatrixToAction.JoinRoom(info.roomItem.id, info.viaServers)) + } + } + Membership.JOIN -> { + if (info.roomType == RoomType.SPACE) { + sharedViewModel.handle(MatrixToAction.OpenSpace(info.roomItem.id)) + } else { + sharedViewModel.handle(MatrixToAction.OpenRoom(info.roomItem.id)) + } + } + else -> { + } + } + } + is RoomInfoResult.PartialInfo -> { + } + else -> { + } + } + } + + fun secondaryButtonClicked() = withState(sharedViewModel) { state -> + } +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt new file mode 100644 index 0000000000..3792183bca --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToUserFragment.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2021 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.matrixto + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentMatrixToUserCardBinding +import im.vector.app.features.home.AvatarRenderer +import javax.inject.Inject + +class MatrixToUserFragment @Inject constructor( + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment() { + + private val sharedViewModel: MatrixToBottomSheetViewModel by parentFragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentMatrixToUserCardBinding { + return FragmentMatrixToUserCardBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + views.matrixToCardSendMessageButton.debouncedClicks { + withState(sharedViewModel) { + it.matrixItem.invoke()?.let { item -> + sharedViewModel.handle(MatrixToAction.StartChattingWithUser(item)) + } + } + } + } + + override fun invalidate() = withState(sharedViewModel) { state -> + when (val item = state.matrixItem) { + Uninitialized -> { + views.matrixToCardUserContentVisibility.isVisible = false + } + is Loading -> { + views.matrixToCardUserContentVisibility.isVisible = false + } + is Success -> { + views.matrixToCardUserContentVisibility.isVisible = true + views.matrixToCardNameText.setTextOrHide(item.invoke().displayName) + views.matrixToCardUserIdText.setTextOrHide(item.invoke().id) + avatarRenderer.render(item.invoke(), views.matrixToCardAvatar) + } + is Fail -> { + // TODO display some error copy? + sharedViewModel.handle(MatrixToAction.FailedToResolveUser) + } + } + + when (state.startChattingState) { + Uninitialized -> { + views.matrixToCardButtonLoading.isVisible = false + views.matrixToCardSendMessageButton.isVisible = false + } + is Success -> { + views.matrixToCardButtonLoading.isVisible = false + views.matrixToCardSendMessageButton.isVisible = true + } + is Fail -> { + views.matrixToCardButtonLoading.isVisible = false + views.matrixToCardSendMessageButton.isVisible = true + // TODO display some error copy? + sharedViewModel.handle(MatrixToAction.FailedToStartChatting) + } + is Loading -> { + views.matrixToCardButtonLoading.isVisible = true + views.matrixToCardSendMessageButton.isInvisible = true + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt index f9491fd361..2c7bc07b23 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt @@ -20,5 +20,7 @@ import im.vector.app.core.platform.VectorViewEvents sealed class MatrixToViewEvents : VectorViewEvents { data class NavigateToRoom(val roomId: String) : MatrixToViewEvents() + data class NavigateToSpace(val spaceId: String) : MatrixToViewEvents() + data class ShowModalError(val error: String) : MatrixToViewEvents() object Dismiss : MatrixToViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index d43337fab0..ca1a010a03 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -78,12 +78,12 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti buildTask: Boolean ): Single { return when (permalinkData) { - is PermalinkData.RoomLink -> { + is PermalinkData.RoomLink -> { permalinkData.getRoomId() .observeOn(AndroidSchedulers.mainThread()) .map { val roomId = it.getOrNull() - if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) { + if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) { openRoom( context = context, roomId = roomId, @@ -94,11 +94,11 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti true } } - is PermalinkData.GroupLink -> { + is PermalinkData.GroupLink -> { navigator.openGroupDetail(permalinkData.groupId, context, buildTask) Single.just(true) } - is PermalinkData.UserLink -> { + is PermalinkData.UserLink -> { if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) { navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask) } @@ -196,7 +196,7 @@ interface NavigationInterceptor { /** * Return true if the navigation has been intercepted */ - fun navToRoom(roomId: String?, eventId: String? = null): Boolean { + fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri? = null): Boolean { return false } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index cee30e7def..8214b26fea 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomdirectory +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem @@ -127,7 +128,7 @@ class PublicRoomsFragment @Inject constructor( val permalink = session.permalinkService().createPermalink(roomIdOrAlias) permalinkHandler .launch(requireContext(), permalink, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { requireActivity().finish() return false } diff --git a/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml b/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml index 37f9633728..652ec8421d 100644 --- a/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml +++ b/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml @@ -1,6 +1,5 @@ - - - - + android:layout_height="wrap_content" /> - - - - - - - - - + diff --git a/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml b/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml new file mode 100644 index 0000000000..6f49f15842 --- /dev/null +++ b/vector/src/main/res/layout/fragment_matrix_to_room_space_card.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_matrix_to_user_card.xml b/vector/src/main/res/layout/fragment_matrix_to_user_card.xml new file mode 100644 index 0000000000..03599779f1 --- /dev/null +++ b/vector/src/main/res/layout/fragment_matrix_to_user_card.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 272bec27fe..93c42dc723 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3286,6 +3286,9 @@ Join my space %1$s %2$s Skip for now + Join Space + Join Anyway + This alias is not accessible at this time.\nTry again later, or ask a room admin to check if you have access. From bb0b1ed0983059be2e2584b74ef430e797f5ef81 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 3 Mar 2021 15:16:46 +0100 Subject: [PATCH 116/230] Some fixes + join actions --- .../matrixto/MatrixToBottomSheetViewModel.kt | 15 ++----- .../matrixto/MatrixToRoomSpaceFragment.kt | 14 ++++++- .../features/navigation/DefaultNavigator.kt | 24 +++++++++++ .../app/features/navigation/Navigator.kt | 2 + .../features/permalink/PermalinkHandler.kt | 40 +++++-------------- 5 files changed, 51 insertions(+), 44 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index 267322b58a..e608774f33 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -47,7 +47,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.util.awaitCallback -import timber.log.Timber class MatrixToBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: MatrixToBottomSheetState, @@ -118,7 +117,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } ?.getOrNull() ?.roomId?.let { - session.getRoom(permalinkData.roomIdOrAlias) + session.getRoom(it) } } else { session.getRoom(permalinkData.roomIdOrAlias) @@ -142,7 +141,6 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } else { - val result = when (val peekResult = tryOrNull { resolveRoom(permalinkData.roomIdOrAlias) }) { is PeekResult.Success -> { RoomInfoResult.FullInfo( @@ -163,7 +161,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } PeekResult.UnknownAlias -> { - RoomInfoResult.NotFound + RoomInfoResult.UnknownAlias(permalinkData.roomIdOrAlias) } null -> { RoomInfoResult.PartialInfo( @@ -201,12 +199,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( * main thing is trying to see if it's a space or a room */ private suspend fun resolveRoom(roomIdOrAlias: String): PeekResult { - return tryOrNull { // this should not throw as it returns a result, but better be safe - awaitCallback { - session.peekRoom(roomIdOrAlias, it) - } + return awaitCallback { + session.peekRoom(roomIdOrAlias, it) } - ?: PeekResult.PeekingNotAllowed(roomIdOrAlias, null, emptyList()) } companion object : MvRxViewModelFactory { @@ -258,7 +253,6 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( copy(startChattingState = Uninitialized) } } - } } @@ -280,7 +274,6 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( copy(startChattingState = Uninitialized) } } - } } diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 8ab2a46bad..31dc537395 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -87,17 +87,23 @@ class MatrixToRoomSpaceFragment @Inject constructor( views.matrixToMemberPills.isVisible = false } + val joinTextRes = if (peek.roomType == RoomType.SPACE) { + R.string.join_space + } else { + R.string.join_room + } + when (peek.membership) { Membership.LEAVE, Membership.NONE -> { views.matrixToCardMainButton.isVisible = true - views.matrixToCardMainButton.text = getString(R.string.join_space) + views.matrixToCardMainButton.text = getString(joinTextRes) views.matrixToCardSecondaryButton.isVisible = false } Membership.INVITE -> { views.matrixToCardMainButton.isVisible = true views.matrixToCardSecondaryButton.isVisible = true - views.matrixToCardMainButton.text = getString(R.string.join_space) + views.matrixToCardMainButton.text = getString(joinTextRes) views.matrixToCardSecondaryButton.text = getString(R.string.decline) } Membership.JOIN -> { @@ -196,6 +202,10 @@ class MatrixToRoomSpaceFragment @Inject constructor( } } is RoomInfoResult.PartialInfo -> { + // we can try to join anyway + if (info.roomId != null) { + sharedViewModel.handle(MatrixToAction.JoinRoom(info.roomId, info.viaServers)) + } } else -> { } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 80cb82098b..27c29ae42b 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -54,6 +54,7 @@ import im.vector.app.features.home.room.detail.search.SearchActivity import im.vector.app.features.home.room.detail.search.SearchArgs import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.invite.InviteUsersToRoomActivity +import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.BigImageViewerActivity import im.vector.app.features.media.VectorAttachmentViewerActivity @@ -119,6 +120,12 @@ class DefaultNavigator @Inject constructor( val args = RoomDetailArgs(roomId, eventId = null, openShareSpaceForId = spaceId.takeIf { openShareSheet }) val intent = RoomDetailActivity.newIntent(context, args) startActivity(context, intent, false) + } else { + // go back to home if we are showing room details? + // This is a bit ugly, but the navigator is supposed to know about the activity stack + if (context is RoomDetailActivity) { + context.finish() + } } } @@ -225,6 +232,23 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } + override fun openMatrixToBottomSheet(context: Context, link: String) { + if (context is AppCompatActivity) { + val listener = object : MatrixToBottomSheet.InteractionListener { + override fun navigateToRoom(roomId: String) { + openRoom(context, roomId) + } + + override fun switchToSpace(spaceId: String) { + this@DefaultNavigator.switchToSpace(context, spaceId, null, openShareSheet = false) + } + } + // TODO check if there is already one?? + MatrixToBottomSheet.withLink(link, listener) + .show(context.supportFragmentManager, "HA#MatrixToBottomSheet") + } + } + override fun openRoomDirectory(context: Context, initialFilter: String) { val intent = RoomDirectoryActivity.getIntent(context, initialFilter) context.startActivity(intent) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 225a2b89ab..489cd37987 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -58,6 +58,8 @@ interface Navigator { fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData) + fun openMatrixToBottomSheet(context: Context, link: String) + fun openCreateRoom(context: Context, initialName: String = "") fun openCreateDirectRoom(context: Context) diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index ca1a010a03..1fea25159f 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -22,7 +22,6 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.toast import im.vector.app.features.navigation.Navigator -import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers @@ -88,6 +87,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti context = context, roomId = roomId, permalinkData = permalinkData, + rawLink = rawLink, buildTask = buildTask ) } @@ -137,6 +137,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti context: Context, roomId: String?, permalinkData: PermalinkData.RoomLink, + rawLink: Uri, buildTask: Boolean ) { val session = activeSessionHolder.getSafeActiveSession() ?: return @@ -152,40 +153,17 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti return when { membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room) membership?.isActive().orFalse() -> { - if (isSpace) { -// navigator.switchToSpace(context, roomId, null) - navigator.openSpacePreview(context, roomId) - } else { + if (!isSpace && membership == Membership.JOIN) { + // If it's a room you're in, let's just open it, you can tap back if needed navigator.openRoom(context, roomId, eventId, buildTask) + } else { + // maybe open space preview navigator.openSpacePreview(context, roomId)? if already joined? + navigator.openMatrixToBottomSheet(context, rawLink.toString()) } } else -> { - if (isSpace) { - navigator.openSpacePreview(context, roomId) - } else { - if (roomSummary == null) { - // we don't know this room, try to peek - val roomPreviewData = RoomPreviewData( - roomId = roomId, - roomAlias = roomAlias, - peekFromServer = true, - buildTask = buildTask, - homeServers = permalinkData.viaParameters - ) - navigator.openRoomPreview(context, roomPreviewData) - } else { - val roomPreviewData = RoomPreviewData( - roomId = roomId, - eventId = eventId, - roomAlias = roomAlias ?: roomSummary.canonicalAlias, - roomName = roomSummary.displayName, - avatarUrl = roomSummary.avatarUrl, - buildTask = buildTask, - homeServers = permalinkData.viaParameters - ) - navigator.openRoomPreview(context, roomPreviewData) - } - } + // XXX this could trigger another server load + navigator.openMatrixToBottomSheet(context, rawLink.toString()) } } } From 80f1c6cb2d08b4edd4af2798d6f453ec6a004e70 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 11 Mar 2021 22:15:40 +0100 Subject: [PATCH 117/230] post rebase fix --- .../vector/app/features/home/room/detail/RoomDetailFragment.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 4d7f9b64f0..df38511b70 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -95,9 +95,7 @@ import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.KnownCallsViewHolder -import im.vector.app.core.ui.views.ActiveConferenceView import im.vector.app.core.ui.views.FailedMessagesWarningView -import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.utils.Debouncer import im.vector.app.core.utils.DimensionConverter From 0c5ca9f51b97431ddad655a927fcbe7909d3f26e Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 11 Mar 2021 22:35:11 +0100 Subject: [PATCH 118/230] Space hierachy SDK updates --- .../org/matrix/android/sdk/rx/RxSession.kt | 10 +- .../sdk/session/space/SpaceHierarchyTest.kt | 373 ++++++++++++++++++ .../android/sdk/api/session/room/Room.kt | 3 + .../sdk/api/session/room/RoomService.kt | 5 + .../session/room/RoomSummaryQueryParams.kt | 25 +- .../sdk/api/session/room/model/RoomSummary.kt | 26 +- .../api/session/room/model/SpaceChildInfo.kt | 10 +- .../{IRoomSummary.kt => SpaceParentInfo.kt} | 21 +- .../android/sdk/api/session/space/Space.kt | 4 +- .../sdk/api/session/space/SpaceService.kt | 14 +- .../sdk/api/session/space/SpaceSummary.kt | 27 -- .../session/space/model/SpaceParentContent.kt | 6 - .../matrix/android/sdk/api/util/MatrixItem.kt | 3 - .../database/RealmSessionStoreMigration.kt | 38 +- .../database/mapper/RoomSummaryMapper.kt | 26 +- .../database/mapper/SpaceSummaryMapper.kt | 40 -- .../sdk/internal/database/model/RoomEntity.kt | 1 - .../database/model/RoomSummaryEntity.kt | 4 +- .../database/model/SessionRealmModule.kt | 4 +- ...foEntity.kt => SpaceChildSummaryEntity.kt} | 20 +- .../model/SpaceParentSummaryEntity.kt | 49 +++ .../database/model/SpaceSummaryEntity.kt | 41 -- .../query/SpaceSummaryEntityQueries.kt | 55 --- .../sdk/internal/session/room/DefaultRoom.kt | 11 + .../session/room/DefaultRoomService.kt | 20 +- .../sdk/internal/session/room/SpaceGetter.kt | 4 +- ...shipHelper.kt => RoomChildRelationInfo.kt} | 39 +- .../room/summary/HierarchyLiveDataHelper.kt | 67 ++++ .../room/summary/RoomSummaryDataSource.kt | 166 +++++++- .../room/summary/RoomSummaryUpdater.kt | 85 ++-- .../internal/session/space/CreateSpaceTask.kt | 8 +- .../internal/session/space/DefaultSpace.kt | 7 +- .../session/space/DefaultSpaceService.kt | 85 +++- .../internal/session/space/JoinSpaceTask.kt | 37 +- .../session/space/SpaceSummaryDataSource.kt | 93 ----- .../internal/session/sync/RoomSyncHandler.kt | 3 + .../grouplist/SelectedSpaceDataSource.kt | 4 +- .../app/features/home/HomeDetailFragment.kt | 6 +- .../app/features/home/HomeDetailViewState.kt | 3 +- .../timeline/factory/TimelineItemFactory.kt | 10 +- .../timeline/format/NoticeEventFormatter.kt | 2 + .../matrixto/MatrixToRoomSpaceFragment.kt | 2 +- .../features/navigation/DefaultNavigator.kt | 2 +- .../settings/SharedPreferenceLiveData.kt | 54 +++ .../features/settings/VectorPreferences.kt | 9 + .../settings/VectorSettingsLabsFragment.kt | 10 +- .../features/spaces/ShareSpaceBottomSheet.kt | 2 +- .../app/features/spaces/SpaceListFragment.kt | 6 +- .../features/spaces/SpaceSummaryController.kt | 22 +- .../app/features/spaces/SpaceSummaryItem.kt | 25 ++ .../features/spaces/SpacesListViewModel.kt | 55 ++- .../spaces/explore/SpaceDirectoryViewModel.kt | 4 +- .../spaces/preview/SpacePreviewViewModel.kt | 12 +- vector/src/main/res/layout/item_group.xml | 2 +- vector/src/main/res/layout/item_space.xml | 20 +- vector/src/main/res/values/strings.xml | 3 +- .../src/main/res/xml/vector_settings_labs.xml | 3 +- 57 files changed, 1211 insertions(+), 475 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/{IRoomSummary.kt => SpaceParentInfo.kt} (61%) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/{SpaceChildInfoEntity.kt => SpaceChildSummaryEntity.kt} (69%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/{RoomRelationshipHelper.kt => RoomChildRelationInfo.kt} (64%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/SharedPreferenceLiveData.kt diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index 7cc0d69bb9..38b0a3a343 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -39,7 +39,6 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.user.model.User @@ -68,13 +67,20 @@ class RxSession(private val session: Session) { } } - fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable> { + fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable> { return session.spaceService().getSpaceSummariesLive(queryParams).asObservable() .startWithCallable { session.spaceService().getSpaceSummaries(queryParams) } } + fun liveFlattenRoomSummaryChildOf(spaceId: String?): Observable> { + return session.getFlattenRoomSummaryChildOfLive(spaceId).asObservable() + .startWithCallable { + session.getFlattenRoomSummaryChildOf(spaceId) + } + } + fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable> { return session.getBreadcrumbsLive(queryParams).asObservable() .startWithCallable { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt new file mode 100644 index 0000000000..b512983ea6 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -0,0 +1,373 @@ +/* + * Copyright 2021 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.session.space + +import android.util.Log +import androidx.lifecycle.Observer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class SpaceHierarchyTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + + @Test + fun createCanonicalChildRelation() { + val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true)) + val spaceName = "My Space" + val topic = "A public space for test" + val spaceId: String + runBlocking { + spaceId = session.spaceService().createSpace(spaceName, topic, null, true) + // wait a bit to let the summry update it self :/ + delay(400) + } + + val syncedSpace = session.spaceService().getSpace(spaceId) + + val roomId = commonTestHelper.doSync { + session.createRoom(CreateRoomParams().apply { name = "General" }, it) + } + + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + + runBlocking { + syncedSpace!!.addChildren(roomId, viaServers, null, true) + } + + runBlocking { + session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers) + } + + Thread.sleep(9000) + + val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents + val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true } + + parents?.forEach { + Log.d("## TEST", "parent : $it") + } + + assertNotNull(parents) + assertEquals(1, parents.size) + assertEquals(spaceName, parents.first().roomSummary?.name) + + assertNotNull(canonicalParents) + assertEquals(1, canonicalParents.size) + assertEquals(spaceName, canonicalParents.first().roomSummary?.name) + } + + @Test + fun testCreateChildRelations() { + val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true)) + val spaceName = "My Space" + val topic = "A public space for test" + Log.d("## TEST", "Before") + val spaceId = runBlocking { + session.spaceService().createSpace(spaceName, topic, null, true) + } + + Log.d("## TEST", "created space $spaceId ${Thread.currentThread()}") + val syncedSpace = session.spaceService().getSpace(spaceId) + + val children = listOf("General" to true /*canonical*/, "Random" to false) + + val roomIdList = children.map { + commonTestHelper.doSync { cb -> + session.createRoom(CreateRoomParams().apply { name = it.first }, cb) + } to it.second + } + + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + + runBlocking { + roomIdList.forEach { entry -> + syncedSpace!!.addChildren(entry.first, viaServers, null, true) + } + } + + runBlocking { + roomIdList.forEach { + session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers) + } + delay(400) + } + + roomIdList.forEach { + val parents = session.getRoom(it.first)?.roomSummary()?.spaceParents + val canonicalParents = session.getRoom(it.first)?.roomSummary()?.spaceParents?.filter { it.canonical == true } + + assertNotNull(parents) + assertEquals(1, parents.size, "Unexpected number of parent") + assertEquals(spaceName, parents.first().roomSummary?.name, "Unexpected parent name ") + assertEquals(if (it.second) 1 else 0, canonicalParents?.size ?: 0, "Parent of ${it.first} should be canonical ${it.second}") + } + } + + @Test + fun testFilteringBySpace() { + val session = commonTestHelper.createAccount("John", SessionTestParams(true)) + + val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + )) + + val spaceBInfo = createPublicSpace(session, "SpaceB", listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + )) + + val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + )) + + // add C as a subspace of A + val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + runBlocking { + spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) + } + + // Create orphan rooms + + val orphan1 = commonTestHelper.doSync { cb -> + session.createRoom(CreateRoomParams().apply { name = "O1" }, cb) + } + val orphan2 = commonTestHelper.doSync { cb -> + session.createRoom(CreateRoomParams().apply { name = "O2" }, cb) + } + + val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) }) + + assertEquals(9, allRooms.size, "Unexpected number of rooms") + + val orphans = session.getFlattenRoomSummaryChildOf(null) + + assertEquals(2, orphans.size, "Unexpected number of orphan rooms") + assertTrue(orphans.indexOfFirst { it.roomId == orphan1 } != -1, "O1 should be an orphan") + assertTrue(orphans.indexOfFirst { it.roomId == orphan2 } != -1, "O2 should be an orphan ${orphans.map { it.name }}") + + val aChildren = session.getFlattenRoomSummaryChildOf(spaceAInfo.spaceId) + + assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms") + assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A") + assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A") + assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A") + assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A") + + // Add a non canonical child and check that it does not appear as orphan + val a3 = commonTestHelper.doSync { cb -> + session.createRoom(CreateRoomParams().apply { name = "A3" }, cb) + } + runBlocking { + spaceA!!.addChildren(a3, viaServers, null, false) + delay(400) + // here we do not set the parent!! + } + + val orphansUpdate = session.getFlattenRoomSummaryChildOf(null) + assertEquals(2, orphansUpdate.size, "Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}") + } + + @Test + fun testBreakCycle() { + val session = commonTestHelper.createAccount("John", SessionTestParams(true)) + + val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + )) + + val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + )) + + // add C as a subspace of A + val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + runBlocking { + spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers) + } + + // add back A as subspace of C + runBlocking { + val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId) + spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true) + } + + Thread.sleep(1000) + + // A -> C -> A + + val aChildren = session.getFlattenRoomSummaryChildOf(spaceAInfo.spaceId) + + assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms ${aChildren.map { it.name }}") + assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A") + assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A") + assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A") + assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A") + } + + @Test + fun testLiveFlatChildren() { + val session = commonTestHelper.createAccount("John", SessionTestParams(true)) + + val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + Triple("A1", true /*auto-join*/, true/*canonical*/), + Triple("A2", true, true) + )) + + val spaceBInfo = createPublicSpace(session, "SpaceB", listOf( + Triple("B1", true /*auto-join*/, true/*canonical*/), + Triple("B2", true, true), + Triple("B3", true, true) + )) + + // add B as a subspace of A + val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + runBlocking { + spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true) + session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers) + } + + val flatAChildren = runBlocking(Dispatchers.Main) { + session.getFlattenRoomSummaryChildOfLive(spaceAInfo.spaceId) + } + + commonTestHelper.waitWithLatch { latch -> + + val childObserver = object : Observer> { + override fun onChanged(children: List?) { +// Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}") + System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}") + if (children?.indexOfFirst { it.name == "C1" } != -1 + && children?.indexOfFirst { it.name == "C2" } != -1 + ) { + // B1 has been added live! + latch.countDown() + flatAChildren.removeObserver(this) + } + } + } + + val spaceCInfo = createPublicSpace(session, "SpaceC", listOf( + Triple("C1", true /*auto-join*/, true/*canonical*/), + Triple("C2", true, true) + )) + + // add C as subspace of B + runBlocking { + val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) + spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + } + + // C1 and C2 should be in flatten child of A now + + GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) } + } + + // Test part one of the rooms + + val bRoomId = spaceBInfo.roomIds.first() + val bRoom = session.getRoom(bRoomId) + + commonTestHelper.waitWithLatch { latch -> + + val childObserver = object : Observer> { + override fun onChanged(children: List?) { + System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}") + if (children?.any { it.roomId == bRoomId } == false) { + // B1 has been added live! + latch.countDown() + flatAChildren.removeObserver(this) + } + } + } + + // part from b room + commonTestHelper.doSync { + bRoom!!.leave(null, it) + } + // The room should have disapear from flat children + GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) } + } + } + + data class TestSpaceCreationResult( + val spaceId: String, + val roomIds: List + ) + + private fun createPublicSpace(session: Session, + spaceName: String, + childInfo: List> + /** Name, auto-join, canonical*/ + ): TestSpaceCreationResult { + val spaceId = runBlocking { + session.spaceService().createSpace(spaceName, "Test Topic", null, true) + } + + val syncedSpace = session.spaceService().getSpace(spaceId) + val viaServers = listOf(session.sessionParams.homeServerHost ?: "") + + val roomIds = + childInfo.map { entry -> + commonTestHelper.doSync { cb -> + session.createRoom(CreateRoomParams().apply { name = entry.first }, cb) + } + } + + roomIds.forEachIndexed { index, roomId -> + runBlocking { + syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second) + val canonical = childInfo[index].third + if (canonical != null) { + session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers) + } + } + } + return TestSpaceCreationResult(spaceId, roomIds) + } +} 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 257c83564e..5f2bc716f6 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 @@ -34,6 +34,8 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.search.SearchResult +import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional /** @@ -90,5 +92,6 @@ interface Room : limit: Int, beforeLimit: Int, afterLimit: Int, +// fun getSpaceParents(): List includeProfile: Boolean): SearchResult } 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 22045366cb..5296ad58b0 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 @@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData import androidx.paging.PagedList import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams @@ -197,4 +198,8 @@ interface RoomService { .setEnablePlaceholders(false) .setPrefetchDistance(10) .build() + + fun getFlattenRoomSummaryChildOf(spaceId: String?, memberships: List = Membership.activeMemberships()) : List + + fun getFlattenRoomSummaryChildOfLive(spaceId: String?, memberships: List = Membership.activeMemberships()): LiveData> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index c8d52302e9..47618e31c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -21,11 +21,27 @@ import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams { return RoomSummaryQueryParams.Builder().apply(init).build() } +fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): SpaceSummaryQueryParams { + return RoomSummaryQueryParams.Builder() + .apply(init) + .apply { + this.includeType = listOf(RoomType.SPACE) + this.excludeType = null + this.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + }.build() +} + +enum class RoomCategoryFilter { + ONLY_DM, + ONLY_ROOMS, + ALL +} /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] @@ -37,7 +53,8 @@ data class RoomSummaryQueryParams( val memberships: List, val roomCategoryFilter: RoomCategoryFilter?, val roomTagQueryFilter: RoomTagQueryFilter? - val excludeType: List + val excludeType: List?, + val includeType: List? ) { class Builder { @@ -49,6 +66,7 @@ data class RoomSummaryQueryParams( var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL var roomTagQueryFilter: RoomTagQueryFilter? = null var excludeType: List = listOf(RoomType.SPACE) + var includeType: List? = null fun build() = RoomSummaryQueryParams( roomId = roomId, @@ -56,8 +74,9 @@ data class RoomSummaryQueryParams( canonicalAlias = canonicalAlias, memberships = memberships, roomCategoryFilter = roomCategoryFilter, - roomTagQueryFilter = roomTagQueryFilter - excludeType = excludeType + roomTagQueryFilter = roomTagQueryFilter, + excludeType = excludeType, + includeType = includeType ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index ac87a16911..f08f605a24 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -27,18 +27,18 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent * It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] */ data class RoomSummary constructor( - override val roomId: String, + val roomId: String, // Computed display name - override val displayName: String = "", - override val name: String = "", - override val topic: String = "", - override val avatarUrl: String = "", - override val canonicalAlias: String? = null, - override val aliases: List = emptyList(), - override val joinedMembersCount: Int? = 0, - override val invitedMembersCount: Int? = 0, + val displayName: String = "", + val name: String = "", + val topic: String = "", + val avatarUrl: String = "", + val canonicalAlias: String? = null, + val aliases: List = emptyList(), + val joinedMembersCount: Int? = 0, + val invitedMembersCount: Int? = 0, val latestPreviewableEvent: TimelineEvent? = null, - override val otherMemberIds: List = emptyList(), + val otherMemberIds: List = emptyList(), val isDirect: Boolean = false, val notificationCount: Int = 0, val highlightCount: Int = 0, @@ -55,8 +55,10 @@ data class RoomSummary constructor( val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null, val hasFailedSending: Boolean = false, - override val roomType: String? = null -) : IRoomSummary { + val roomType: String? = null, + val spaceParents: List? = null, + val children: List? = null +) { val isVersioned: Boolean get() = versioningState != VersioningState.NONE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt index 38d9f1e74e..1302a34daa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt @@ -17,8 +17,16 @@ package org.matrix.android.sdk.api.session.room.model data class SpaceChildInfo( - val roomSummary: IRoomSummary?, + val childRoomId: String, + // We might not know this child at all, + // i.e we just know it exists but no info on type/name/etc.. + val isKnown: Boolean, + val roomType: String?, + val name: String?, + val topic: String?, + val avatarUrl: String?, val order: String?, + val activeMemberCount: Int?, val autoJoin: Boolean, val viaServers: List ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt similarity index 61% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt index 1724f00c99..5ed81b0646 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/IRoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt @@ -5,7 +5,7 @@ * 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 + * 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, @@ -16,16 +16,9 @@ package org.matrix.android.sdk.api.session.room.model -interface IRoomSummary { - val roomId: String - val displayName: String - val name: String - val topic: String - val avatarUrl: String - val canonicalAlias: String? - val aliases: List - val joinedMembersCount: Int? - val invitedMembersCount: Int? - val otherMemberIds: List - val roomType: String? -} +data class SpaceParentInfo( + val parentId: String?, + val roomSummary: RoomSummary?, + val canonical: Boolean?, + val viaServers: List +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 0172b3701b..cfbffc128c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -24,9 +24,9 @@ interface Space { fun asRoom() : Room /** - * A current snapshot of [RoomSummary] associated with the room + * A current snapshot of [RoomSummary] associated with the space */ - fun spaceSummary(): SpaceSummary? + fun spaceSummary(): RoomSummary? suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean = false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index 3e30f14748..15b6f7d852 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -60,9 +60,9 @@ interface SpaceService { * Get a live list of space summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[SpaceSummary] */ - fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> + fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> - fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List + fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List sealed class JoinSpaceResult { object Success : JoinSpaceResult() @@ -79,4 +79,14 @@ interface SpaceService { viaServers: List = emptyList()): JoinSpaceResult suspend fun rejectInvite(spaceId: String, reason: String?) + +// fun getSpaceParentsOfRoom(roomId: String) : List + + /** + * Let this room declare that it has a parent. + * @param canonical true if it should be the main parent of this room + * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced: + * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering. + */ + suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt deleted file mode 100644 index 1473ed7a96..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceSummary.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.space - -import org.matrix.android.sdk.api.session.room.model.IRoomSummary -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo - -data class SpaceSummary( - val spaceId: String, - val roomSummary: RoomSummary, - val children: List -) : IRoomSummary by roomSummary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt index b3f7267580..871a494914 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt @@ -26,7 +26,6 @@ import com.squareup.moshi.JsonClass * "state_key": "!space:example.com", * "content": { * "via": ["example.com"], - * "present": true, * "canonical": true, * } * } @@ -38,11 +37,6 @@ data class SpaceParentContent( * Parents where via is not present are ignored. */ @Json(name = "via") val via: List? = null, - /** - * present: true key is included to distinguish from a deleted state event - * Parent where present is not present (sic) or is not set to true are ignored. - */ - @Json(name = "present") val present: Boolean? = false, /** * Canonical determines whether this is the main parent for the space. * When a user joins a room with a canonical parent, clients may switch to view the room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt index a792248764..a904b43faa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.sender.SenderInfo -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.user.model.User import java.util.Locale @@ -152,8 +151,6 @@ fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatar fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) -fun SpaceSummary.toMatrixItem() = MatrixItem.RoomItem(spaceId, displayName, avatarUrl) - // If no name is available, use room alias as Riot-Web does fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index b14ed9e9c7..9dd8cda8b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm -import io.realm.FieldAttribute import io.realm.RealmMigration import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields @@ -31,8 +30,8 @@ import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.model.SpaceChildInfoEntityFields -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import timber.log.Timber import javax.inject.Inject @@ -200,21 +199,34 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { fun migrateTo10(realm: DynamicRealm) { Timber.d("Step 9 -> 10") + realm.schema.create("SpaceChildSummaryEntity") + ?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java) + ?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java) + ?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java) + ?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true) + ?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + +// realm.schema.create("SpaceSummaryEntity") +// ?.addField(SpaceSummaryEntityFields.SPACE_ID, String::class.java, FieldAttribute.PRIMARY_KEY) +// ?.setRequired(SpaceSummaryEntityFields.SPACE_ID, true) +// ?.addRealmObjectField(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) +// ?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, spaceChildInfoSchema!!) + + realm.schema.create("SpaceParentSummaryEntity") + ?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java) + ?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java) + ?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true) +// ?.addRealmListField(RoomParentRelationInfoEntityFields.VIA_SERVERS.`$`, String::class.java) +// ?.addRealmObjectField(RoomParentRelationInfoEntityFields.SPACE_SUMMARY_ENTITY.`$`, realm.schema.get("SpaceSummaryEntity")!!) + ?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + realm.schema.get("RoomSummaryEntity") ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java) ?.transform { obj -> // Should I put messaging type here? obj.setString(RoomSummaryEntityFields.ROOM_TYPE, null) } - - val spaceChildInfoSchema = realm.schema.create("SpaceChildInfoEntity") - ?.addField(SpaceChildInfoEntityFields.ORDER, String::class.java) - ?.addRealmListField(SpaceChildInfoEntityFields.VIA_SERVERS.`$`, String::class.java) - ?.addRealmObjectField(SpaceChildInfoEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) - - realm.schema.create("SpaceSummaryEntity") - ?.addField(SpaceSummaryEntityFields.SPACE_ID, String::class.java, FieldAttribute.PRIMARY_KEY) - ?.addRealmObjectField(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) - ?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, spaceChildInfoSchema!!) + ?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!) + ?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index c74eb4460d..410500ed8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker @@ -64,7 +66,29 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel, inviterId = roomSummaryEntity.inviterId, hasFailedSending = roomSummaryEntity.hasFailedSending, - roomType = roomSummaryEntity.roomType + roomType = roomSummaryEntity.roomType, + spaceParents = roomSummaryEntity.parents.map { relationInfoEntity -> + SpaceParentInfo( + parentId = relationInfoEntity.parentRoomId, + roomSummary = relationInfoEntity.parentSummaryEntity?.let { map(it) }, + canonical = relationInfoEntity.canonical ?: false, + viaServers = relationInfoEntity.viaServers.toList() + ) + }, + children = roomSummaryEntity.children.map { + SpaceChildInfo( + childRoomId = it.childRoomId ?: "", + isKnown = it.childSummaryEntity != null, + roomType = it.childSummaryEntity?.roomType, + name = it.childSummaryEntity?.name, + topic = it.childSummaryEntity?.topic, + avatarUrl = it.childSummaryEntity?.avatarUrl, + activeMemberCount = it.childSummaryEntity?.joinedMembersCount, + order = it.order, + autoJoin = it.autoJoin ?: false, + viaServers = it.viaServers.toList() + ) + } ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt deleted file mode 100644 index d08528598d..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/SpaceSummaryMapper.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.database.mapper - -import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo -import org.matrix.android.sdk.api.session.space.SpaceSummary -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity -import javax.inject.Inject - -internal class SpaceSummaryMapper @Inject constructor(private val roomSummaryMapper: RoomSummaryMapper) { - - fun map(spaceSummaryEntity: SpaceSummaryEntity): SpaceSummary { - return SpaceSummary( - spaceId = spaceSummaryEntity.spaceId, - roomSummary = roomSummaryMapper.map(spaceSummaryEntity.roomSummaryEntity!!), - children = spaceSummaryEntity.children.map { - SpaceChildInfo( - roomSummary = it.roomSummaryEntity?.let { rs -> roomSummaryMapper.map(rs) }, - autoJoin = it.autoJoin ?: false, - viaServers = it.viaServers.map { it }, - order = it.order - ) - } - ) - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt index 3ff2532604..58297776f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt @@ -43,6 +43,5 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "", set(value) { membersLoadStatusStr = value.name } - companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index c87ac15a78..85e4595da5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -28,6 +28,9 @@ import org.matrix.android.sdk.api.session.room.model.tag.RoomTag internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "" + var roomType: String? = null, + var parents: RealmList = RealmList(), + var children: RealmList = RealmList() ) : RealmObject() { var displayName: String? = "" @@ -244,6 +247,5 @@ internal open class RoomSummaryEntity( roomEncryptionTrustLevelStr = value?.name } } - companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 76116be1a8..72ae512fa5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -62,7 +62,7 @@ import io.realm.annotations.RealmModule UserAccountDataEntity::class, ScalarTokenEntity::class, WellknownIntegrationManagerConfigEntity::class, - SpaceSummaryEntity::class, - SpaceChildInfoEntity::class + SpaceChildSummaryEntity::class, + SpaceParentSummaryEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt similarity index 69% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt index 7862207901..982c9ece6a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt @@ -22,14 +22,22 @@ import io.realm.RealmObject /** * Decorates room summary with space related information. */ -internal open class SpaceChildInfoEntity( - var viaServers: RealmList = RealmList(), - // Use for alphabetic ordering of this child +internal open class SpaceChildSummaryEntity( +// var isSpace: Boolean = false, + var order: String? = null, - // If true, this child should be join when parent is joined + var autoJoin: Boolean? = null, - // link to the actual room (check type to see if it's a subspace) - var roomSummaryEntity: RoomSummaryEntity? = null + + var childRoomId: String? = null, + // Link to the actual space summary if it is known locally + var childSummaryEntity: RoomSummaryEntity? = null, + + var viaServers: RealmList = RealmList() +// var owner: RoomSummaryEntity? = null, + +// var level: Int = 0 + ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt new file mode 100644 index 0000000000..af32cd2b83 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt @@ -0,0 +1,49 @@ +/* + * 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.database.model + +import io.realm.RealmList +import io.realm.RealmObject + +/** + * Decorates room summary with space related information. + */ +internal open class SpaceParentSummaryEntity( + /** + * Determines whether this is the main parent for the space + * When a user joins a room with a canonical parent, clients may switch to view the room in the context of that space, + * peeking into it in order to find other rooms and group them together. + * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced: + * if multiple are present the client should select the one with the lowest room ID, + * as determined via a lexicographic utf-8 ordering. + */ + var canonical: Boolean? = null, + + var parentRoomId: String? = null, + // Link to the actual space summary if it is known locally + var parentSummaryEntity: RoomSummaryEntity? = null, + + var viaServers: RealmList = RealmList() + +// var child: RoomSummaryEntity? = null, + +// var level: Int = 0 + +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt deleted file mode 100644 index e63b5b9d55..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceSummaryEntity.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.database.model - -import io.realm.RealmList -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey - -internal open class SpaceSummaryEntity(@PrimaryKey var spaceId: String = "", - var roomSummaryEntity: RoomSummaryEntity? = null, - var children: RealmList = RealmList() - // TODO public / private .. and more -) : RealmObject() { - - // Do we want to denormalize that ? - -// private var membershipStr: String = Membership.NONE.name -// var membership: Membership -// get() { -// return Membership.valueOf(membershipStr) -// } -// set(value) { -// membershipStr = value.name -// } - - companion object -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt deleted file mode 100644 index b6403c596f..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/SpaceSummaryEntityQueries.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.database.query - -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.kotlin.createObject -import io.realm.kotlin.where -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields - -internal fun SpaceSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { - val query = realm.where() - query.isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`) - if (roomId != null) { - query.equalTo(SpaceSummaryEntityFields.SPACE_ID, roomId) - } - query.sort(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME) - return query -} - -internal fun SpaceSummaryEntity.Companion.findByAlias(realm: Realm, roomAlias: String): SpaceSummaryEntity? { - val spaceSummary = realm.where() - .isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`) - .equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, roomAlias) - .findFirst() - if (spaceSummary != null) { - return spaceSummary - } - return realm.where() - .isNotNull(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`) - .contains(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.FLAT_ALIASES, "|$roomAlias") - .findFirst() -} - -internal fun SpaceSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String): SpaceSummaryEntity { - return where(realm, roomId).findFirst() ?: realm.createObject(roomId).also { - it.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) - } -} 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 1d8eb6c95e..1c22faa7b8 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 @@ -24,6 +24,7 @@ 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 +import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.relation.RelationService import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService import org.matrix.android.sdk.api.session.room.read.ReadService @@ -36,11 +37,16 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.search.SearchResult +import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.search.SearchTask +import org.matrix.android.sdk.internal.session.space.DefaultSpace +import org.matrix.android.sdk.internal.task.TaskExecutor +import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback import java.security.InvalidParameterException import javax.inject.Inject @@ -148,4 +154,9 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, ) ) } + + override fun asSpace(): Space? { + if (roomSummary()?.roomType != RoomType.SPACE) return null + return DefaultSpace(this, roomSummaryDataSource) + } } 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 4724167e87..cd39e633dd 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 @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams @@ -33,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper 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 @@ -63,7 +65,9 @@ internal class DefaultRoomService @Inject constructor( private val peekRoomTask: PeekRoomTask, private val roomGetter: RoomGetter, private val roomSummaryDataSource: RoomSummaryDataSource, - private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource + private val roomSummaryMapper: RoomSummaryMapper, + private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, + private val taskExecutor: TaskExecutor ) : RoomService { override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable { @@ -168,4 +172,18 @@ internal class DefaultRoomService @Inject constructor( override suspend fun peekRoom(roomIdOrAlias: String): PeekResult { return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias)) } + + override fun getFlattenRoomSummaryChildOf(spaceId: String?, memberships: List): List { + if (spaceId == null) { + return roomSummaryDataSource.getFlattenOrphanRooms() + } + return roomSummaryDataSource.getAllRoomSummaryChildOf(spaceId, memberships) + } + + override fun getFlattenRoomSummaryChildOfLive(spaceId: String?, memberships: List): LiveData> { + if (spaceId == null) { + return roomSummaryDataSource.getFlattenOrphanRoomsLive() + } + return roomSummaryDataSource.getAllRoomSummaryChildOfLive(spaceId, memberships) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt index f440a67710..0f64bb60ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt @@ -18,8 +18,8 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.space.DefaultSpace -import org.matrix.android.sdk.internal.session.space.SpaceSummaryDataSource import javax.inject.Inject internal interface SpaceGetter { @@ -28,7 +28,7 @@ internal interface SpaceGetter { internal class DefaultSpaceGetter @Inject constructor( private val roomGetter: RoomGetter, - private val spaceSummaryDataSource: SpaceSummaryDataSource + private val spaceSummaryDataSource: RoomSummaryDataSource ) : SpaceGetter { override fun get(spaceId: String): Space? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt similarity index 64% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt index c04f5d3948..06ab21d8db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomRelationshipHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt @@ -20,6 +20,7 @@ import io.realm.Realm 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.space.model.SpaceChildContent +import org.matrix.android.sdk.api.session.space.model.SpaceParentContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.query.whereType @@ -35,8 +36,8 @@ import timber.log.Timber * * - Separately, rooms can claim parents via the m.room.parent state event: */ -internal class RoomRelationshipHelper(private val realm: Realm, - private val roomId: String +internal class RoomChildRelationInfo(private val realm: Realm, + private val roomId: String ) { data class SpaceChildInfo( @@ -46,15 +47,24 @@ internal class RoomRelationshipHelper(private val realm: Realm, val viaServers: List ) + data class SpaceParentInfo( + val roomId: String, + val canonical: Boolean, + val viaServers: List, + val stateEventSender: String + ) + /** * Gets the ordered list of valid child description. */ fun getDirectChildrenDescriptions(): List { return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD) - .findAll() + .findAll().also { + Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId") + } .mapNotNull { ContentMapper.map(it.root?.content).toModel()?.let { scc -> - Timber.d("## Space child desc state event $scc") + Timber.v("## Space child desc state event $scc") // Children where via is not present are ignored. scc.via?.let { via -> SpaceChildInfo( @@ -68,4 +78,25 @@ internal class RoomRelationshipHelper(private val realm: Realm, } .sortedBy { it.order } } + + fun getParentDescriptions(): List { + return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT) + .findAll().also { + Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId") + } + .mapNotNull { + ContentMapper.map(it.root?.content).toModel()?.let { scc -> + Timber.v("## Space parent desc state event $scc") + // Parent where via is not present are ignored. + scc.via?.let { via -> + SpaceParentInfo( + roomId = it.stateKey, + canonical = scc.canonical ?: false, + viaServers = via, + stateEventSender = it.root?.sender ?: "" + ) + } + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt new file mode 100644 index 0000000000..ced1ad963e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2021 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.summary + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.Optional + +internal class HierarchyLiveDataHelper( + val spaceId: String, + val memberships: List, + val roomSummaryDataSource: RoomSummaryDataSource) { + + private val sources = HashMap>>() + private val mediatorLiveData = MediatorLiveData>() + + fun liveData() = mediatorLiveData + + init { + onChange() + } + + private fun parentsToCheck(): List { + val spaces = ArrayList() + roomSummaryDataSource.getSpaceSummary(spaceId)?.let { + roomSummaryDataSource.flattenSubSpace(it, emptyList(), spaces, memberships) + } + return spaces + } + + private fun onChange() { + val existingSources = sources.keys.toList() + val newSources = parentsToCheck().map { it.roomId } + val addedSources = newSources.filter { !existingSources.contains(it) } + val removedSource = existingSources.filter { !newSources.contains(it) } + addedSources.forEach { + val liveData = roomSummaryDataSource.getSpaceSummaryLive(it) + mediatorLiveData.addSource(liveData) { onChange() } + sources[it] = liveData + } + + removedSource.forEach { + sources[it]?.let { mediatorLiveData.removeSource(it) } + } + + sources[spaceId]?.value?.getOrNull()?.let { spaceSummary -> + val results = ArrayList() + roomSummaryDataSource.flattenChild(spaceSummary, emptyList(), results, memberships) + mediatorLiveData.postValue(results.map { it.roomId }) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 576e7f4eba..2468661ada 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -1,5 +1,6 @@ /* * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2021 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. @@ -27,9 +28,17 @@ import io.realm.Sort import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult +import io.realm.kotlin.where +import org.matrix.android.sdk.api.session.room.RoomCategoryFilter +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams +import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper @@ -84,6 +93,36 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat ) } + fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { + return getRoomSummariesLive(queryParams) + } + + fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? { + return getRoomSummary(roomIdOrAlias).let { + it?.takeIf { it.roomType == RoomType.SPACE } + } + } + + fun getSpaceSummaryLive(roomId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm -> + RoomSummaryEntity.where(realm, roomId) + .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) + .equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE) + }, + { + roomSummaryMapper.map(it) + } + ) + return Transformations.map(liveData) { results -> + results.firstOrNull().toOptional() + } + } + + fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { + return getRoomSummaries(spaceSummaryQueryParams) + } + fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List { return monarchy.fetchAllMappedSync( { breadcrumbsQuery(it, queryParams) }, @@ -190,9 +229,134 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } } - queryParams.excludeType.forEach { + queryParams.excludeType?.forEach { query.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, it) } + queryParams.includeType?.forEach { + query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it) + } + queryParams.roomCategoryFilter?.let { + when (it) { + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ALL -> { + // nop + } + } + } return query } + + fun getAllRoomSummaryChildOf(spaceAliasOrId: String, memberShips: List): List { + val space = getSpaceSummary(spaceAliasOrId) ?: return emptyList() + val result = ArrayList() + flattenChild(space, emptyList(), result, memberShips) + return result + } + + fun getAllRoomSummaryChildOfLive(spaceId: String, memberShips: List): LiveData> { + // we want to listen to all spaces in hierarchy and on change compute back all childs + // and switch map to listen thoose? + val mediatorLiveData = HierarchyLiveDataHelper(spaceId, memberShips, this).liveData() + + return Transformations.switchMap(mediatorLiveData) { allIds -> + monarchy.findAllMappedWithChanges( + { + it.where() + .`in`(RoomSummaryEntityFields.ROOM_ID, allIds.toTypedArray()) + .`in`(RoomSummaryEntityFields.MEMBERSHIP_STR, memberShips.map { it.name }.toTypedArray()) + .equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + }, + { + roomSummaryMapper.map(it) + }) + } + } + + fun getFlattenOrphanRooms(): List { + return getRoomSummaries(roomSummaryQueryParams { + memberships = Membership.activeMemberships() + excludeType = listOf(RoomType.SPACE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + }).filter { + // we need to check if orphan + isOrphan(it) + } + } + + fun getFlattenOrphanRoomsLive(): LiveData> { + return Transformations.map( + getRoomSummariesLive(roomSummaryQueryParams { + memberships = Membership.activeMemberships() + excludeType = listOf(RoomType.SPACE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + })) { + it.filter { + isOrphan(it) + } + } + } + + private fun isOrphan(roomSummary: RoomSummary): Boolean { + if (roomSummary.roomType == RoomType.SPACE && roomSummary.membership.isActive()) { + return false + } + // all parents line should be orphan + roomSummary.spaceParents?.forEach { info -> + if (info.roomSummary != null && !info.roomSummary.membership.isLeft()) { + if (!isOrphan(info.roomSummary)) { + return false + } + } + } + + // it may not have a parent relation but could be a child of some other.... + for (spaceSummary in getSpaceSummaries(spaceSummaryQueryParams { memberships = Membership.activeMemberships() })) { + if (spaceSummary.children?.any { it.childRoomId == roomSummary.roomId } == true) { + return false + } + } + + return true + } + + fun flattenChild(current: RoomSummary, parenting: List, output: MutableList, memberShips: List) { + current.children?.sortedBy { it.order ?: it.name }?.forEach { childInfo -> + if (childInfo.roomType == RoomType.SPACE) { + // Add recursive + if (!parenting.contains(childInfo.childRoomId)) { // avoid cycles! + getSpaceSummary(childInfo.childRoomId)?.let { subSpace -> + if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) { + flattenChild(subSpace, parenting + listOf(current.roomId), output, memberShips) + } + } + } + } else if (childInfo.isKnown) { + getRoomSummary(childInfo.childRoomId)?.let { + if (memberShips.isEmpty() || memberShips.contains(it.membership)) { + if (!it.isDirect) { + output.add(it) + } + } + } + } + } + } + + fun flattenSubSpace(current: RoomSummary, parenting: List, output: MutableList, memberShips: List) { + output.add(current) + current.children?.sortedBy { it.order ?: it.name }?.forEach { + if (it.roomType == RoomType.SPACE) { + // Add recursive + if (!parenting.contains(it.childRoomId)) { // avoid cycles! + getSpaceSummary(it.childRoomId)?.let { subSpace -> + if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) { + output.add(subSpace) + flattenSubSpace(subSpace, parenting + listOf(current.roomId), output, memberShips) + } + } + } + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 3d01021811..e7cb9688dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -39,19 +39,22 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.SpaceChildInfoEntity -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntity +import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.isEventRead +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.extensions.clearWith import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import org.matrix.android.sdk.internal.session.room.relationship.RoomRelationshipHelper +import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications import timber.log.Timber @@ -62,7 +65,8 @@ internal class RoomSummaryUpdater @Inject constructor( private val roomDisplayNameResolver: RoomDisplayNameResolver, private val roomAvatarResolver: RoomAvatarResolver, private val eventDecryptor: EventDecryptor, - private val crossSigningService: DefaultCrossSigningService) { + private val crossSigningService: DefaultCrossSigningService, + private val stateEventDataSource: StateEventDataSource) { fun update(realm: Realm, roomId: String, @@ -163,28 +167,6 @@ internal class RoomSummaryUpdater @Inject constructor( crossSigningService.onUsersDeviceUpdate(otherRoomMembers) } } - - if (roomType == RoomType.SPACE) { - Timber.v("## Space: Updating summary for Space $roomId membership: ${roomSummaryEntity.membership}") - val spaceSummaryEntity = SpaceSummaryEntity() - spaceSummaryEntity.spaceId = roomId - spaceSummaryEntity.roomSummaryEntity = roomSummaryEntity - spaceSummaryEntity.children.clear() - spaceSummaryEntity.children.addAll( - RoomRelationshipHelper(realm, roomId).getDirectChildrenDescriptions() - .map { - Timber.v("## Space: Updating summary for room $roomId with info $it") - realm.createObject().apply { - this.roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, it.roomId) - this.order = it.order - this.autoJoin = it.autoJoin - }.also { - Timber.v("## Space: Updating summary for room $roomId with children $it") - } - } - ) - realm.insertOrUpdate(spaceSummaryEntity) - } } private fun RoomSummaryEntity.updateHasFailedSending() { @@ -196,4 +178,55 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.updateHasFailedSending() roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) } + + /** + * Should be called at the end of the room sync, to check and validate all parent/child relations + */ + fun validateSpaceRelationship(realm: Realm) { + // Do level 0 stuffs + + realm.where(RoomSummaryEntity::class.java).findAll().forEach { roomSummary -> + if (roomSummary.roomType == RoomType.SPACE) { + roomSummary.children.clearWith { it.deleteFromRealm() } + roomSummary.children.addAll( + RoomChildRelationInfo(realm, roomSummary.roomId).getDirectChildrenDescriptions() + .map { + Timber.v("## Space: Updating summary for room ${roomSummary.roomId} with info $it") + realm.createObject().apply { + this.childRoomId = it.roomId + this.childSummaryEntity = RoomSummaryEntity.where(realm, it.roomId).findFirst() + this.order = it.order + this.autoJoin = it.autoJoin + this.viaServers.addAll(it.viaServers) +// this.level = 0 + }.also { + Timber.v("## Space: Updating summary for room ${roomSummary.roomId} with children $it") + } + } + ) + } + + // check parents + roomSummary.parents.clearWith { it.deleteFromRealm() } + roomSummary.parents.addAll( + RoomChildRelationInfo(realm, roomSummary.roomId).getParentDescriptions() + .map { parentInfo -> + Timber.v("## Space: Updating summary for room ${roomSummary.roomId} with parent info $parentInfo") + realm.createObject().apply { + this.parentRoomId = parentInfo.roomId + this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst() + this.canonical = parentInfo.canonical + this.viaServers.addAll(parentInfo.viaServers) +// this.level = 0 + }.also { + Timber.v("## Space: Updating summary for room ${roomSummary.roomId} with parent $it") + } + } + ) + } + } + +// private fun isValidCanonical() : Boolean { +// +// } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt index 5f174587d0..826fa76f77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/CreateSpaceTask.kt @@ -21,8 +21,8 @@ import kotlinx.coroutines.TimeoutCancellationException 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.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.task.Task @@ -44,8 +44,8 @@ internal class DefaultCreateSpaceTask @Inject constructor( try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - realm.where(SpaceSummaryEntity::class.java) - .equalTo(SpaceSummaryEntityFields.SPACE_ID, spaceId) + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, spaceId) } } catch (exception: TimeoutCancellationException) { throw CreateRoomFailure.CreatedWithTimeout(spaceId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index efba103ab7..13b9465d58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -21,17 +21,18 @@ 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.events.model.toModel import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.space.Space -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.model.SpaceChildContent +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource -internal class DefaultSpace(private val room: Room, private val spaceSummaryDataSource: SpaceSummaryDataSource) : Space { +internal class DefaultSpace(private val room: Room, private val spaceSummaryDataSource: RoomSummaryDataSource) : Space { override fun asRoom(): Room { return room } - override fun spaceSummary(): SpaceSummary? { + override fun spaceSummary(): RoomSummary? { return spaceSummaryDataSource.getSpaceSummary(asRoom().roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 0210c81e5d..cdd2673fde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -19,26 +19,37 @@ package org.matrix.android.sdk.internal.session.space import android.net.Uri import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent 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.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.SpaceService -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.session.space.model.SpaceChildContent +import org.matrix.android.sdk.api.session.space.model.SpaceParentContent import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.RoomGetter import org.matrix.android.sdk.internal.session.room.SpaceGetter import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult +import java.lang.IllegalArgumentException import javax.inject.Inject internal class DefaultSpaceService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, + @UserId private val userId: String, private val createSpaceTask: CreateSpaceTask, // private val joinRoomTask: JoinRoomTask, private val joinSpaceTask: JoinSpaceTask, @@ -47,8 +58,9 @@ internal class DefaultSpaceService @Inject constructor( // private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, // private val roomIdByAliasTask: GetRoomIdByAliasTask, // private val deleteRoomAliasTask: DeleteRoomAliasTask, -// private val roomGetter: RoomGetter, - private val spaceSummaryDataSource: SpaceSummaryDataSource, + private val roomGetter: RoomGetter, + private val roomSummaryDataSource: RoomSummaryDataSource, + private val stateEventDataSource: StateEventDataSource, private val peekSpaceTask: PeekSpaceTask, private val resolveSpaceInfoTask: ResolveSpaceInfoTask, private val leaveRoomTask: LeaveRoomTask @@ -73,12 +85,12 @@ internal class DefaultSpaceService @Inject constructor( return spaceGetter.get(spaceId) } - override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { - return spaceSummaryDataSource.getRoomSummariesLive(queryParams) + override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { + return roomSummaryDataSource.getSpaceSummariesLive(queryParams) } - override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { - return spaceSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) + override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { + return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) } override suspend fun peekSpace(spaceId: String): SpacePeekResult { @@ -108,21 +120,16 @@ internal class DefaultSpaceService @Inject constructor( ?.firstOrNull { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD } ?.content.toModel() SpaceChildInfo( - roomSummary = RoomSummary( - roomId = childSummary.roomId, - roomType = childSummary.roomType, - name = childSummary.name ?: "", - displayName = childSummary.name ?: "", - topic = childSummary.topic ?: "", - joinedMembersCount = childSummary.numJoinedMembers, - avatarUrl = childSummary.avatarUrl ?: "", - encryptionEventTs = null, - typingUsers = emptyList(), - isEncrypted = false - ), + childRoomId = childSummary.roomId, + isKnown = true, + roomType = childSummary.roomType, + name = childSummary.name, + topic = childSummary.topic, + avatarUrl = childSummary.avatarUrl, order = childStateEv?.order, autoJoin = childStateEv?.autoJoin ?: false, - viaServers = childStateEv?.via ?: emptyList() + viaServers = childStateEv?.via ?: emptyList(), + activeMemberCount = childSummary.numJoinedMembers ) } ?: emptyList() ) @@ -138,4 +145,42 @@ internal class DefaultSpaceService @Inject constructor( override suspend fun rejectInvite(spaceId: String, reason: String?) { leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason)) } + +// override fun getSpaceParentsOfRoom(roomId: String): List { +// return spaceSummaryDataSource.getParentsOfRoom(roomId) +// } + + override suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List) { + // Should we perform some validation here?, + // and if client want to bypass, it could use sendStateEvent directly? + if (canonical) { + // check that we can send m.child in the parent room + if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) { + throw UnsupportedOperationException("Cannot add canonical child if not member of parent") + } + val powerLevelsEvent = stateEventDataSource.getStateEvent( + roomId = parentSpaceId, + eventType = EventType.STATE_ROOM_POWER_LEVELS, + stateKey = QueryStringValue.NoCondition + ) + val powerLevelsContent = powerLevelsEvent?.content?.toModel() + ?: throw UnsupportedOperationException("Cannot add canonical child, not enough power level") + val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent) + if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) { + throw UnsupportedOperationException("Cannot add canonical child, not enough power level") + } + } + + val room = roomGetter.getRoom(childRoomId) + ?: throw IllegalArgumentException("Unknown Room $childRoomId") + + room.sendStateEvent( + eventType = EventType.STATE_SPACE_PARENT, + stateKey = parentSpaceId, + body = SpaceParentContent( + via = viaServers, + canonical = canonical + ).toContent() + ) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index 6ee3652761..9379c0d650 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -22,11 +22,12 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.task.Task import timber.log.Timber import java.util.concurrent.TimeUnit @@ -45,7 +46,7 @@ internal class DefaultJoinSpaceTask @Inject constructor( private val joinRoomTask: JoinRoomTask, @SessionDatabase private val realmConfiguration: RealmConfiguration, - private val spaceSummaryDataSource: SpaceSummaryDataSource + private val roomSummaryDataSource: RoomSummaryDataSource ) : JoinSpaceTask { override suspend fun execute(params: JoinSpaceTask.Params): SpaceService.JoinSpaceResult { @@ -65,15 +66,15 @@ internal class DefaultJoinSpaceTask @Inject constructor( Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...") try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm -> - realm.where(SpaceSummaryEntity::class.java) + realm.where(RoomSummaryEntity::class.java) .apply { if (params.roomIdOrAlias.startsWith("!")) { - equalTo(SpaceSummaryEntityFields.SPACE_ID, params.roomIdOrAlias) + equalTo(RoomSummaryEntityFields.ROOM_ID, params.roomIdOrAlias) } else { - equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, params.roomIdOrAlias) + equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, params.roomIdOrAlias) } } - .equalTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.MEMBERSHIP_STR, Membership.JOIN.name) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } } catch (exception: TimeoutCancellationException) { Timber.w("## Space: > Error created with timeout") @@ -83,21 +84,21 @@ internal class DefaultJoinSpaceTask @Inject constructor( val errors = HashMap() Timber.v("## Space: > Sync done ...") // after that i should have the children (? do I need to paginate to get state) - val summary = spaceSummaryDataSource.getSpaceSummary(params.roomIdOrAlias) - Timber.v("## Space: Found space summary Name:[${summary?.roomSummary?.name}] children: ${summary?.children?.size}") + val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias) + Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.children?.size}") summary?.children?.forEach { - val childRoomSummary = it.roomSummary ?: return@forEach - Timber.v("## Space: Processing child :[${childRoomSummary.roomId}] autoJoin:${it.autoJoin}") +// val childRoomSummary = it.roomSummary ?: return@forEach + Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}") if (it.autoJoin) { // I should try to join as well - if (childRoomSummary.roomType == RoomType.SPACE) { + if (it.roomType == RoomType.SPACE) { // recursively join auto-joined child of this space? - when (val subspaceJoinResult = this.execute(JoinSpaceTask.Params(it.roomSummary.roomId, null, it.viaServers))) { + when (val subspaceJoinResult = this.execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) { SpaceService.JoinSpaceResult.Success -> { // nop } is SpaceService.JoinSpaceResult.Fail -> { - errors[it.roomSummary.roomId] = subspaceJoinResult.error + errors[it.childRoomId] = subspaceJoinResult.error } is SpaceService.JoinSpaceResult.PartialSuccess -> { errors.putAll(subspaceJoinResult.failedRooms) @@ -105,15 +106,15 @@ internal class DefaultJoinSpaceTask @Inject constructor( } } else { try { - Timber.v("## Space: Joining room child ${childRoomSummary.roomId}") + Timber.v("## Space: Joining room child ${it.childRoomId}") joinRoomTask.execute(JoinRoomTask.Params( - roomIdOrAlias = childRoomSummary.roomId, + roomIdOrAlias = it.childRoomId, reason = "Auto-join parent space", viaServers = it.viaServers )) } catch (failure: Throwable) { - errors[it.roomSummary.roomId] = failure - Timber.e("## Space: Failed to join room child ${childRoomSummary.roomId}") + errors[it.childRoomId] = failure + Timber.e("## Space: Failed to join room child ${it.childRoomId}") } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt deleted file mode 100644 index c15e81c287..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryDataSource.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.space - -import androidx.lifecycle.LiveData -import androidx.lifecycle.Transformations -import com.zhuinden.monarchy.Monarchy -import io.realm.Realm -import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams -import org.matrix.android.sdk.api.session.room.model.VersioningState -import org.matrix.android.sdk.api.session.space.SpaceSummary -import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams -import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.database.mapper.SpaceSummaryMapper -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntity -import org.matrix.android.sdk.internal.database.model.SpaceSummaryEntityFields -import org.matrix.android.sdk.internal.database.query.findByAlias -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.query.process -import org.matrix.android.sdk.internal.util.fetchCopyMap -import javax.inject.Inject - -internal class SpaceSummaryDataSource @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, - private val spaceSummaryMapper: SpaceSummaryMapper -) { - - fun getSpaceSummary(roomIdOrAlias: String): SpaceSummary? { - return monarchy - .fetchCopyMap({ - if (roomIdOrAlias.startsWith("!")) { - // It's a roomId - SpaceSummaryEntity.where(it, roomId = roomIdOrAlias).findFirst() - } else { - // Assume it's a room alias - SpaceSummaryEntity.findByAlias(it, roomIdOrAlias) - } - }, { entity, _ -> - spaceSummaryMapper.map(entity) - }) - } - - fun getSpaceSummaryLive(roomId: String): LiveData> { - val liveData = monarchy.findAllMappedWithChanges( - { realm -> SpaceSummaryEntity.where(realm, roomId).isNotEmpty(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME) }, - { spaceSummaryMapper.map(it) } - ) - return Transformations.map(liveData) { results -> - results.firstOrNull().toOptional() - } - } - - fun getSpaceSummaries(queryParams: SpaceSummaryQueryParams): List { - return monarchy.fetchAllMappedSync( - { spaceSummariesQuery(it, queryParams) }, - { spaceSummaryMapper.map(it) } - ) - } - - fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { - return monarchy.findAllMappedWithChanges( - { spaceSummariesQuery(it, queryParams) }, - { spaceSummaryMapper.map(it) } - ) - } - - private fun spaceSummariesQuery(realm: Realm, queryParams: SpaceSummaryQueryParams): RealmQuery { - val query = SpaceSummaryEntity.where(realm) - query.process(SpaceSummaryEntityFields.SPACE_ID, queryParams.roomId) - query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.DISPLAY_NAME, queryParams.displayName) - query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.CANONICAL_ALIAS, queryParams.canonicalAlias) - query.process(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.MEMBERSHIP_STR, queryParams.memberships) - query.notEqualTo(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) - return query - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 95fbb2f1b0..5a1de41b95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -95,6 +95,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter) handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter) handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter) + + // post room sync validation + roomSummaryUpdater.validateSpaceRelationship(realm) } // PRIVATE METHODS ***************************************************************************** diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt b/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt index d95251c271..e8293d7e99 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/grouplist/SelectedSpaceDataSource.kt @@ -18,9 +18,9 @@ package im.vector.app.features.grouplist import arrow.core.Option import im.vector.app.core.utils.BehaviorDataSource -import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject import javax.inject.Singleton @Singleton -class SelectedSpaceDataSource @Inject constructor() : BehaviorDataSource>(Option.empty()) +class SelectedSpaceDataSource @Inject constructor() : BehaviorDataSource>(Option.empty()) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 6bc3f27fd4..faf85124d3 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -55,7 +55,7 @@ import im.vector.app.features.workers.signout.BannerState import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.api.session.space.SpaceSummary +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import timber.log.Timber @@ -252,10 +252,10 @@ class HomeDetailFragment @Inject constructor( } } - private fun onSpaceChange(spaceSummary: SpaceSummary?) { + private fun onSpaceChange(spaceSummary: RoomSummary?) { spaceSummary?.let { // Use GlideApp with activity context to avoid the glideRequests to be paused - if (spaceSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { + if (spaceSummary.roomId == ALL_COMMUNITIES_GROUP_ID) { // Special case views.groupToolbarAvatarImageView.background = ContextCompat.getDrawable(requireContext(), R.drawable.space_home_background) views.groupToolbarAvatarImageView.scaleType = ImageView.ScaleType.CENTER_INSIDE diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index dd316dcece..d2ca7e9115 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -22,12 +22,11 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.session.sync.SyncState data class HomeDetailViewState( val groupSummary: Option = Option.empty(), - val spaceSummary: Option = Option.empty(), + val spaceSummary: Option = Option.empty(), val asyncRooms: Async> = Uninitialized, val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE, val notificationCountCatchup: Int = 0, 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 47bc60eb75..06541c1fc3 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 @@ -60,19 +60,21 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_GUEST_ACCESS, - EventType.REDACTION, EventType.STATE_ROOM_ALIASES, - EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_MAC, EventType.CALL_CANDIDATES, + EventType.KEY_VERIFICATION_MAC, EventType.CALL_REPLACES, EventType.CALL_SELECT_ANSWER, EventType.CALL_NEGOTIATE, EventType.REACTION, - EventType.STATE_ROOM_POWER_LEVELS -> noticeItemFactory.create(params) + EventType.STATE_ROOM_POWER_LEVELS, + EventType.STATE_SPACE_CHILD, + EventType.STATE_SPACE_PARENT, + EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_WIDGET_LEGACY, EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params) EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params) 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 878cec0a07..71504daeff 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 @@ -107,6 +107,8 @@ class NoticeEventFormatter @Inject constructor( EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_READY, + EventType.STATE_SPACE_CHILD, + EventType.STATE_SPACE_PARENT, EventType.REDACTION -> formatDebug(timelineEvent.root) else -> { Timber.v("Type $type not handled by this formatter") diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt index 31dc537395..a7b5543157 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToRoomSpaceFragment.kt @@ -212,6 +212,6 @@ class MatrixToRoomSpaceFragment @Inject constructor( } } - fun secondaryButtonClicked() = withState(sharedViewModel) { state -> + private fun secondaryButtonClicked() = withState(sharedViewModel) { _ -> } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 27c29ae42b..548f0b986d 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -111,7 +111,7 @@ class DefaultNavigator @Inject constructor( } sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId)?.spaceSummary()?.let { - Timber.d("## Nav: Switching to space $spaceId / ${it.roomSummary.name}") + Timber.d("## Nav: Switching to space $spaceId / ${it.name}") selectedSpaceDataSource.post(Option.just(it)) } ?: kotlin.run { Timber.d("## Nav: Failed to switch to space $spaceId") diff --git a/vector/src/main/java/im/vector/app/features/settings/SharedPreferenceLiveData.kt b/vector/src/main/java/im/vector/app/features/settings/SharedPreferenceLiveData.kt new file mode 100644 index 0000000000..08e83258a1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/SharedPreferenceLiveData.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 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.settings + +import android.content.SharedPreferences +import androidx.lifecycle.LiveData + +abstract class SharedPreferenceLiveData(protected val sharedPrefs: SharedPreferences, + protected val key: String, + private val defValue: T) : LiveData() { + + private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key == this.key) { + value = getValueFromPreferences(key, defValue) + } + } + + abstract fun getValueFromPreferences(key: String, defValue: T): T + + override fun onActive() { + super.onActive() + value = getValueFromPreferences(key, defValue) + sharedPrefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener) + } + + override fun onInactive() { + sharedPrefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) + super.onInactive() + } + + companion object { + fun booleanLiveData(sharedPrefs: SharedPreferences, key: String, defaultValue: Boolean): SharedPreferenceLiveData { + return object : SharedPreferenceLiveData(sharedPrefs, key, defaultValue) { + override fun getValueFromPreferences(key: String, defValue: Boolean): Boolean { + return this.sharedPrefs.getBoolean(key, defValue) + } + } + } + } +} 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 222c8da6b7..4d1c9b5728 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 @@ -20,6 +20,7 @@ import android.media.RingtoneManager import android.net.Uri import android.provider.MediaStore import androidx.core.content.edit +import androidx.lifecycle.LiveData import com.squareup.seismic.ShakeDetector import im.vector.app.BuildConfig import im.vector.app.R @@ -312,6 +313,14 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_LABS_USE_SPACES, false) } + fun labSpacesLive(): LiveData { + return SharedPreferenceLiveData.booleanLiveData( + defaultPrefs, + SETTINGS_LABS_USE_SPACES, + false + ) + } + fun failFast(): Boolean { return BuildConfig.DEBUG || (developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY, false)) } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt index 6a4ea83d68..cc2ca4eb4d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsLabsFragment.kt @@ -17,6 +17,9 @@ package im.vector.app.features.settings import im.vector.app.R +import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.features.MainActivity +import im.vector.app.features.MainActivityArgs import javax.inject.Inject class VectorSettingsLabsFragment @Inject constructor( @@ -27,6 +30,11 @@ class VectorSettingsLabsFragment @Inject constructor( override val preferenceXmlRes = R.xml.vector_settings_labs override fun bindPref() { - // Nothing to do + findPreference(VectorPreferences.SETTINGS_LABS_USE_SPACES)!!.let { pref -> + pref.setOnPreferenceChangeListener { _, _ -> + MainActivity.restartApp(requireActivity(), MainActivityArgs(clearCache = false)) + true + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt index d3fb225083..2a6e3dace9 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/ShareSpaceBottomSheet.kt @@ -61,7 +61,7 @@ class ShareSpaceBottomSheet : VectorBaseBottomSheetDialogFragment?, selected: SpaceSummary?) { + private fun buildGroupModels(summaries: List?, selected: RoomSummary?) { if (summaries.isNullOrEmpty()) { return } // show invites on top - summaries.filter { it.roomSummary.membership == Membership.INVITE } + summaries.filter { it.membership == Membership.INVITE } .let { invites -> if (invites.isNotEmpty()) { genericItemHeader { @@ -67,7 +67,7 @@ class SpaceSummaryController @Inject constructor( invites.forEach { spaceSummaryItem { avatarRenderer(avatarRenderer) - id(it.spaceId) + id(it.roomId) matrixItem(it.toMatrixItem()) selected(false) listener { callback?.onSpaceSelected(it) } @@ -87,19 +87,19 @@ class SpaceSummaryController @Inject constructor( } summaries - .filter { it.roomSummary.membership == Membership.JOIN } + .filter { it.membership == Membership.JOIN } .forEach { groupSummary -> - val isSelected = groupSummary.spaceId == selected?.spaceId - if (groupSummary.spaceId == ALL_COMMUNITIES_GROUP_ID) { + val isSelected = groupSummary.roomId == selected?.roomId + if (groupSummary.roomId == ALL_COMMUNITIES_GROUP_ID) { homeSpaceSummaryItem { - id(groupSummary.spaceId) + id(groupSummary.roomId) selected(isSelected) listener { callback?.onSpaceSelected(groupSummary) } } } else { spaceSummaryItem { avatarRenderer(avatarRenderer) - id(groupSummary.spaceId) + id(groupSummary.roomId) matrixItem(groupSummary.toMatrixItem()) selected(isSelected) onLeave { callback?.onLeaveSpace(groupSummary) } @@ -119,8 +119,8 @@ class SpaceSummaryController @Inject constructor( } interface Callback { - fun onSpaceSelected(spaceSummary: SpaceSummary) - fun onLeaveSpace(spaceSummary: SpaceSummary) + fun onSpaceSelected(spaceSummary: RoomSummary) + fun onLeaveSpace(spaceSummary: RoomSummary) fun onAddSpaceSelected() } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index 3f1971d9dc..ffd27446f4 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -18,6 +18,8 @@ package im.vector.app.features.spaces import android.widget.ImageView import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass @@ -37,6 +39,8 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { @EpoxyAttribute var selected: Boolean = false @EpoxyAttribute var listener: (() -> Unit)? = null @EpoxyAttribute var onLeave: (() -> Unit)? = null + @EpoxyAttribute var toggleExpand: (() -> Unit)? = null + @EpoxyAttribute var expanded: Boolean? = null override fun bind(holder: Holder) { super.bind(holder) @@ -52,6 +56,26 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { } else { holder.leaveView.isVisible = false } + + when (expanded) { + null -> { + holder.collapseIndicator.isGone = true + } + else -> { + holder.collapseIndicator.isVisible = true + holder.collapseIndicator.setImageDrawable( + ContextCompat.getDrawable(holder.view.context, + if (expanded!!) R.drawable.ic_expand_less else R.drawable.ic_expand_more + ) + ) + holder.collapseIndicator.setOnClickListener( + DebouncedClickListener({ _ -> + toggleExpand?.invoke() + }) + ) + } + } + avatarRenderer.renderSpace(matrixItem, holder.avatarImageView) } @@ -65,5 +89,6 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { val groupNameView by bind(R.id.groupNameView) val rootView by bind(R.id.itemGroupLayout) val leaveView by bind(R.id.groupTmpLeave) + val collapseIndicator by bind(R.id.groupChildrenCollapse) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index 28bc358e36..de5ca6a137 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -42,15 +42,14 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" sealed class SpaceListAction : VectorViewModelAction { - data class SelectSpace(val spaceSummary: SpaceSummary) : SpaceListAction() - data class LeaveSpace(val spaceSummary: SpaceSummary) : SpaceListAction() + data class SelectSpace(val spaceSummary: RoomSummary) : SpaceListAction() + data class LeaveSpace(val spaceSummary: RoomSummary) : SpaceListAction() object AddSpace : SpaceListAction() } @@ -64,8 +63,8 @@ sealed class SpaceListViewEvents : VectorViewEvents { } data class SpaceListViewState( - val asyncSpaces: Async> = Uninitialized, - val selectedSpace: SpaceSummary? = null + val asyncSpaces: Async> = Uninitialized, + val selectedSpace: RoomSummary? = null ) : MvRxState class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, @@ -96,7 +95,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp selectedSpaceDataSource .observe() .subscribe { - if (currentGroupId != it.orNull()?.spaceId) { + if (currentGroupId != it.orNull()?.roomId) { setState { copy( selectedSpace = it.orNull() @@ -111,8 +110,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp selectSubscribe(SpaceListViewState::selectedSpace) { spaceSummary -> if (spaceSummary != null) { // We only want to open group if the updated selectedGroup is a different one. - if (currentGroupId != spaceSummary.spaceId) { - currentGroupId = spaceSummary.spaceId + if (currentGroupId != spaceSummary.roomId) { + currentGroupId = spaceSummary.roomId _viewEvents.post(SpaceListViewEvents.OpenSpace) } val optionGroup = Option.just(spaceSummary) @@ -120,7 +119,7 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } else { // If selected group is null we force to default. It can happens when leaving the selected group. setState { - copy(selectedSpace = this.asyncSpaces()?.find { it.spaceId == ALL_COMMUNITIES_GROUP_ID }) + copy(selectedSpace = this.asyncSpaces()?.find { it.roomId == ALL_COMMUNITIES_GROUP_ID }) } } } @@ -138,17 +137,17 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> // get uptodate version of the space - val summary = session.spaceService().getSpaceSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Equals(action.spaceSummary.spaceId) }) + val summary = session.spaceService().getSpaceSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Equals(action.spaceSummary.roomId) }) .firstOrNull() - if (summary?.roomSummary?.membership == Membership.INVITE) { - _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary(summary.roomSummary.roomId)) + if (summary?.membership == Membership.INVITE) { + _viewEvents.post(SpaceListViewEvents.OpenSpaceSummary(summary.roomId)) // viewModelScope.launch(Dispatchers.IO) { // tryOrNull { session.spaceService().peekSpace(action.spaceSummary.spaceId) }.let { // Timber.d("PEEK RESULT/ $it") // } // } } else { - if (state.selectedSpace?.spaceId != action.spaceSummary.spaceId) { + if (state.selectedSpace?.roomId != action.spaceSummary.roomId) { // state.selectedSpace?.let { // selectedSpaceDataSource.post(Option.just(state.selectedSpace)) // } @@ -160,8 +159,8 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp private fun handleLeaveSpace(action: SpaceListAction.LeaveSpace) { viewModelScope.launch { awaitCallback { - tryOrNull("Failed to leave space ${action.spaceSummary.spaceId}") { - session.spaceService().getSpace(action.spaceSummary.spaceId)?.asRoom()?.leave(null, it) + tryOrNull("Failed to leave space ${action.spaceSummary.roomId}") { + session.spaceService().getSpace(action.spaceSummary.roomId)?.asRoom()?.leave(null, it) } } } @@ -178,23 +177,19 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp excludeType = listOf(/**RoomType.MESSAGING,$*/ null) } - Observable.combineLatest, List>( + Observable.combineLatest, List>( session .rx() .liveUser(session.myUserId) .map { optionalUser -> - SpaceSummary( - spaceId = ALL_COMMUNITIES_GROUP_ID, - roomSummary = RoomSummary( - roomId = ALL_COMMUNITIES_GROUP_ID, - membership = Membership.JOIN, - displayName = stringProvider.getString(R.string.group_all_communities), - avatarUrl = optionalUser.getOrNull()?.avatarUrl ?: "", - encryptionEventTs = 0, - isEncrypted = false, - typingUsers = emptyList() - ), - children = emptyList() + RoomSummary( + roomId = ALL_COMMUNITIES_GROUP_ID, + membership = Membership.JOIN, + displayName = stringProvider.getString(R.string.group_all_communities), + avatarUrl = optionalUser.getOrNull()?.avatarUrl ?: "", + encryptionEventTs = 0, + isEncrypted = false, + typingUsers = emptyList() ) }, session @@ -205,9 +200,9 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } ) .execute { async -> - val currentSelectedGroupId = selectedSpace?.spaceId + val currentSelectedGroupId = selectedSpace?.roomId val newSelectedGroup = if (currentSelectedGroupId != null) { - async()?.find { it.spaceId == currentSelectedGroupId } + async()?.find { it.roomId == currentSelectedGroupId } } else { async()?.firstOrNull() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt index a5d1e16101..31f7162c2a 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryViewModel.kt @@ -35,8 +35,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.api.session.space.SpaceSummary import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -45,7 +45,7 @@ data class SpaceDirectoryState( // The current filter val spaceId: String, val currentFilter: String = "", - val summary: Async = Uninitialized, + val summary: Async = Uninitialized, // True if more result are available server side val hasMore: Boolean = false, // Set of joined roomId / spaces, diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt index 987884d8c9..66a702f685 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewViewModel.kt @@ -156,12 +156,12 @@ class SpacePreviewViewModel @AssistedInject constructor( childInfoList = Success( resolveResult.second.map { ChildInfo( - roomId = it.roomSummary?.roomId ?: "", - avatarUrl = it.roomSummary?.avatarUrl, - name = it.roomSummary?.name, - topic = it.roomSummary?.topic, - memberCount = it.roomSummary?.joinedMembersCount, - isSubSpace = it.roomSummary?.roomType == RoomType.SPACE, + roomId = it.childRoomId, + avatarUrl = it.avatarUrl, + name = it.name, + topic = it.topic, + memberCount = it.activeMemberCount, + isSubSpace = it.roomType == RoomType.SPACE, children = Uninitialized, viaServers = null ) diff --git a/vector/src/main/res/layout/item_group.xml b/vector/src/main/res/layout/item_group.xml index 6c2721ff33..9cd07f3215 100644 --- a/vector/src/main/res/layout/item_group.xml +++ b/vector/src/main/res/layout/item_group.xml @@ -5,7 +5,7 @@ android:id="@+id/itemGroupLayout" android:layout_width="match_parent" android:layout_height="65dp" - android:background="@drawable/bg_group_item" + android:background="@drawable/bg_space_item" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground"> diff --git a/vector/src/main/res/layout/item_space.xml b/vector/src/main/res/layout/item_space.xml index 25b685b999..92f0fcc1d5 100644 --- a/vector/src/main/res/layout/item_space.xml +++ b/vector/src/main/res/layout/item_space.xml @@ -35,11 +35,28 @@ android:textSize="15sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator" - app:layout_constraintEnd_toStartOf="@+id/groupTmpLeave" + app:layout_constraintEnd_toStartOf="@+id/groupChildrenCollapse" app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/lorem/random" /> + + + Enable swipe to reply in timeline Add a dedicated tab for unread notifications on main screen. - Enable Spaces (formerly known as ‘groups as rooms’) to allow users to organise rooms into more useful groups. + Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features. + Warning: This will trigger a clear cache and initial sync Link copied to clipboard diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index ed1ea222a2..aa55a3c5ec 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -49,6 +49,7 @@ + android:title="@string/labs_experimental_spaces" + android:summary="@string/labs_experimental_spaces_desc"/> \ No newline at end of file From 48fef45ce353ecfbe9b554f0af3ff028f14ae4f6 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 09:36:56 +0100 Subject: [PATCH 119/230] Code quality --- .../database/RealmSessionStoreMigration.kt | 10 ++------- .../room/summary/RoomSummaryDataSource.kt | 22 +++++++++---------- tools/check/forbidden_strings_in_code.txt | 2 +- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 9dd8cda8b9..48479fd746 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -205,20 +205,14 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { ?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java) ?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true) ?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) - -// realm.schema.create("SpaceSummaryEntity") -// ?.addField(SpaceSummaryEntityFields.SPACE_ID, String::class.java, FieldAttribute.PRIMARY_KEY) -// ?.setRequired(SpaceSummaryEntityFields.SPACE_ID, true) -// ?.addRealmObjectField(SpaceSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) -// ?.addRealmListField(SpaceSummaryEntityFields.CHILDREN.`$`, spaceChildInfoSchema!!) + ?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java) realm.schema.create("SpaceParentSummaryEntity") ?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java) ?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java) ?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true) -// ?.addRealmListField(RoomParentRelationInfoEntityFields.VIA_SERVERS.`$`, String::class.java) -// ?.addRealmObjectField(RoomParentRelationInfoEntityFields.SPACE_SUMMARY_ENTITY.`$`, realm.schema.get("SpaceSummaryEntity")!!) ?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + ?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java) realm.schema.get("RoomSummaryEntity") ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 2468661ada..fd54ac63ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -274,14 +274,13 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } fun getFlattenOrphanRooms(): List { - return getRoomSummaries(roomSummaryQueryParams { - memberships = Membership.activeMemberships() - excludeType = listOf(RoomType.SPACE) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - }).filter { - // we need to check if orphan - isOrphan(it) - } + return getRoomSummaries( + roomSummaryQueryParams { + memberships = Membership.activeMemberships() + excludeType = listOf(RoomType.SPACE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ).filter { isOrphan(it) } } fun getFlattenOrphanRoomsLive(): LiveData> { @@ -290,10 +289,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat memberships = Membership.activeMemberships() excludeType = listOf(RoomType.SPACE) roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - })) { - it.filter { - isOrphan(it) - } + }) + ) { + it.filter { isOrphan(it) } } } diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 5a53ececec..a4ec75e19f 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===94 +enum class===97 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From e364a36ee6acbbb65676d3a8b23555a36412d79e Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 09:42:10 +0100 Subject: [PATCH 120/230] cleaning --- .../java/org/matrix/android/sdk/api/session/room/Room.kt | 3 ++- .../internal/database/model/SpaceParentSummaryEntity.kt | 4 ---- .../sdk/internal/session/space/DefaultSpaceService.kt | 7 ------- .../im/vector/app/features/permalink/PermalinkHandler.kt | 2 +- 4 files changed, 3 insertions(+), 13 deletions(-) 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 5f2bc716f6..1bbc090ef0 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 @@ -92,6 +92,7 @@ interface Room : limit: Int, beforeLimit: Int, afterLimit: Int, -// fun getSpaceParents(): List includeProfile: Boolean): SearchResult + + fun asSpace(): Space? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt index af32cd2b83..30517717f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt @@ -39,10 +39,6 @@ internal open class SpaceParentSummaryEntity( var viaServers: RealmList = RealmList() -// var child: RoomSummaryEntity? = null, - -// var level: Int = 0 - ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index cdd2673fde..ab104901d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -51,21 +51,14 @@ internal class DefaultSpaceService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, @UserId private val userId: String, private val createSpaceTask: CreateSpaceTask, -// private val joinRoomTask: JoinRoomTask, private val joinSpaceTask: JoinSpaceTask, private val spaceGetter: SpaceGetter, -// 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 stateEventDataSource: StateEventDataSource, private val peekSpaceTask: PeekSpaceTask, private val resolveSpaceInfoTask: ResolveSpaceInfoTask, private val leaveRoomTask: LeaveRoomTask -// private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, -// private val taskExecutor: TaskExecutor ) : SpaceService { override suspend fun createSpace(params: CreateSpaceParams): String { diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index 1fea25159f..aa977730db 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -148,7 +148,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti val roomSummary = session.getRoomSummary(roomId) val membership = roomSummary?.membership val eventId = permalinkData.eventId - val roomAlias = permalinkData.getRoomAliasOrNull() +// val roomAlias = permalinkData.getRoomAliasOrNull() val isSpace = roomSummary?.roomType == RoomType.SPACE return when { membership == Membership.BAN -> context.toast(R.string.error_opening_banned_room) From 01c56824b7456d1b43660cca6015f216df0a3074 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 10:25:46 +0100 Subject: [PATCH 121/230] small a11Y fix --- vector/src/main/res/layout/item_space_top_summary.xml | 1 + vector/src/main/res/layout/view_space_type_button.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/vector/src/main/res/layout/item_space_top_summary.xml b/vector/src/main/res/layout/item_space_top_summary.xml index e4e2bbdd76..35ca5ebf34 100644 --- a/vector/src/main/res/layout/item_space_top_summary.xml +++ b/vector/src/main/res/layout/item_space_top_summary.xml @@ -11,6 +11,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:src="@drawable/ic_room_profile_member_list" + android:importantForAccessibility="no" app:layout_constraintBottom_toBottomOf="@+id/spaceSummaryMemberCountText" app:layout_constraintStart_toEndOf="@+id/spaceSummaryMemberCountText" app:layout_constraintStart_toStartOf="parent" diff --git a/vector/src/main/res/layout/view_space_type_button.xml b/vector/src/main/res/layout/view_space_type_button.xml index ac20b74d4a..6ddb126469 100644 --- a/vector/src/main/res/layout/view_space_type_button.xml +++ b/vector/src/main/res/layout/view_space_type_button.xml @@ -61,6 +61,7 @@ android:id="@+id/rightChevron" android:layout_width="16dp" android:layout_height="16dp" + android:importantForAccessibility="no" android:src="@drawable/ic_material_chevron_right_black" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" From 0da9be327ac2fc0ba302c6665563600fe22cfc22 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 11:00:39 +0100 Subject: [PATCH 122/230] Removed unneeded id.home menu handling --- .../vector/app/features/spaces/SpaceCreationActivity.kt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt index ed8b85f587..c5dd09ee84 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceCreationActivity.kt @@ -19,7 +19,6 @@ package im.vector.app.features.spaces import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.MenuItem import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import com.airbnb.mvrx.Loading @@ -65,14 +64,6 @@ class SpaceCreationActivity : SimpleFragmentActivity(), CreateSpaceViewModel.Fac } } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return super.onOptionsItemSelected(item) - } - override fun initUiAndData() { super.initUiAndData() viewModel.subscribe(this) { From 802853d205ae9d57e6dcb73bd8fa14a426ca2cbb Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 18 Mar 2021 09:42:21 +0100 Subject: [PATCH 123/230] Suggested Space support --- .../android/sdk/api/session/space/Space.kt | 7 +- .../session/space/model/SpaceChildContent.kt | 9 +- .../internal/session/space/DefaultSpace.kt | 8 +- .../sdk/internal/session/space/SpaceApi.kt | 2 +- .../session/space/SpaceSummaryParams.kt | 5 +- .../session/sync/ReadReceiptHandler.kt | 2 +- .../java/im/vector/app/AppStateHandler.kt | 11 ++ .../im/vector/app/core/di/VectorComponent.kt | 4 + ...CurrentSpaceSuggestedRoomListDataSource.kt | 25 ++++ .../app/features/home/HomeDetailFragment.kt | 3 +- .../features/home/room/list/RoomListAction.kt | 1 + .../home/room/list/RoomListFragment.kt | 5 + .../home/room/list/RoomListViewModel.kt | 38 +++++- .../home/room/list/RoomSummaryItemFactory.kt | 18 +++ .../home/room/list/SuggestedRoomItem.kt | 117 +++++++++++++++++ .../matrixto/MatrixToBottomSheetViewModel.kt | 32 ++++- .../features/spaces/SpacesListViewModel.kt | 5 +- .../spaces/create/CreateSpaceViewModelTask.kt | 16 ++- .../ui/SharedPreferencesUiStateRepository.kt | 14 +- .../app/features/ui/UiStateRepository.kt | 4 + .../main/res/layout/item_suggested_room.xml | 123 ++++++++++++++++++ vector/src/main/res/values/strings.xml | 2 + 22 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/CurrentSpaceSuggestedRoomListDataSource.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomItem.kt create mode 100644 vector/src/main/res/layout/item_suggested_room.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index cfbffc128c..ba76276fde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -21,14 +21,17 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary interface Space { - fun asRoom() : Room + fun asRoom(): Room /** * A current snapshot of [RoomSummary] associated with the space */ fun spaceSummary(): RoomSummary? - suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean = false) + suspend fun addChildren(roomId: String, viaServers: List, + order: String?, + autoJoin: Boolean = false, + suggested: Boolean? = false) suspend fun removeRoom(roomId: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt index f7abf7e618..f84d781eb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -46,7 +46,14 @@ data class SpaceChildContent( * be automatically joined by members of that space. * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.) */ - @Json(name = "auto_join") val autoJoin: Boolean? = false + @Json(name = "auto_join") val autoJoin: Boolean? = false, + + /** + * If `suggested` is set to `true`, that indicates that the child should be advertised to + * members of the space by the client. This could be done by showing them eagerly + * in the room list. This is should be ignored if `auto_join` is set to `true`. + */ + @Json(name = "suggested") val suggested: Boolean? = false ) { /** * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 13b9465d58..76496769ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -36,14 +36,18 @@ internal class DefaultSpace(private val room: Room, private val spaceSummaryData return spaceSummaryDataSource.getSpaceSummary(asRoom().roomId) } - override suspend fun addChildren(roomId: String, viaServers: List, order: String?, autoJoin: Boolean) { + override suspend fun addChildren(roomId: String, viaServers: List, + order: String?, + autoJoin: Boolean, + suggested: Boolean?) { asRoom().sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( via = viaServers, autoJoin = autoJoin, - order = order + order = order, + suggested = suggested ).toContent() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt index 5919a90b99..249ebd2fd2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -35,7 +35,7 @@ internal interface SpaceApi { * * MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md */ - @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/spaces") + @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces") fun getSpaces(@Path("roomId") spaceId: String, @Body params: SpaceSummaryParams ): Call diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt index a3c6b3cc84..af5aec0554 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt @@ -26,5 +26,8 @@ internal data class SpaceSummaryParams( /** The maximum number of rooms/subspaces to return, server can override this, default: 100 */ @Json(name = "limit") val limit: Int = 100, /** A token to use if this is a subsequent HTTP hit, default: "".*/ - @Json(name = "batch") val batch: String = "" + @Json(name = "batch") val batch: String = "", + /** whether we should only return children with the "suggested" flag set.*/ + @Json(name = "suggested_only") val suggestedOnly: Boolean = false + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index e5d9217db7..fc1a2c3870 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -143,7 +143,7 @@ internal class ReadReceiptHandler @Inject constructor( @Suppress("UNCHECKED_CAST") val content = dataFromFile .events - .firstOrNull { it.type == EventType.RECEIPT } + ?.firstOrNull { it.type == EventType.RECEIPT } ?.content as? ReadReceiptContent if (content == null) { diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index edec704f18..22816085fb 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -33,6 +33,17 @@ class AppStateHandler @Inject constructor() : LifecycleObserver { private val compositeDisposable = CompositeDisposable() + init { + // restore current space from ui state + sessionDataSource.currentValue?.orNull()?.let { session -> + uiStateRepository.getSelectedSpace(session.sessionId)?.let { selectedSpaceId -> + session.getRoomSummary(selectedSpaceId)?.let { + selectedSpaceDataSource.post(Option.just(it)) + } + } + } + } + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { } diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index 3a197d3f83..e75f2416eb 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -37,6 +37,8 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.grouplist.SelectedSpaceDataSource import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.CurrentSpaceSuggestedRoomListDataSource +import im.vector.app.features.home.HomeRoomListDataSource import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder @@ -118,6 +120,8 @@ interface VectorComponent { fun selectedSpaceStore(): SelectedSpaceDataSource + fun currentSpaceSuggestedRoomListDataSource(): CurrentSpaceSuggestedRoomListDataSource + fun roomDetailPendingActionStore(): RoomDetailPendingActionStore fun activeSessionObservableStore(): ActiveSessionDataSource diff --git a/vector/src/main/java/im/vector/app/features/home/CurrentSpaceSuggestedRoomListDataSource.kt b/vector/src/main/java/im/vector/app/features/home/CurrentSpaceSuggestedRoomListDataSource.kt new file mode 100644 index 0000000000..21fd37c8fc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/CurrentSpaceSuggestedRoomListDataSource.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.home + +import im.vector.app.core.utils.BehaviorDataSource +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class CurrentSpaceSuggestedRoomListDataSource @Inject constructor() : BehaviorDataSource>() diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index faf85124d3..1f600de9e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -58,7 +58,6 @@ import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo -import timber.log.Timber import javax.inject.Inject private const val INDEX_PEOPLE = 0 @@ -363,7 +362,7 @@ class HomeDetailFragment @Inject constructor( } override fun invalidate() = withState(viewModel) { - Timber.v(it.toString()) +// Timber.v(it.toString()) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index 883efb2e60..37f7d148aa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -29,4 +29,5 @@ sealed class RoomListAction : VectorViewModelAction { data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction() data class ToggleTag(val roomId: String, val tag: String) : RoomListAction() data class LeaveRoom(val roomId: String) : RoomListAction() + data class JoinSuggestedRoom(val roomId: String, val viaServers: List?) : RoomListAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index aaa5bbcde5..edc3de58e0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -52,6 +52,7 @@ import im.vector.app.features.notifications.NotificationDrawerManager import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import javax.inject.Inject @@ -421,6 +422,10 @@ class RoomListFragment @Inject constructor( roomListViewModel.handle(RoomListAction.AcceptInvitation(room)) } + override fun onJoinSuggestedRoom(room: SpaceChildInfo) { + roomListViewModel.handle(RoomListAction.JoinSuggestedRoom(room.childRoomId, room.viaServers)) + } + override fun onRejectRoomInvitation(room: RoomSummary) { notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId) roomListViewModel.handle(RoomListAction.RejectInvitation(room)) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 423a950591..45a6f2218f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -18,8 +18,11 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes 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.ViewModelContext import im.vector.app.R import im.vector.app.core.extensions.exhaustive @@ -166,6 +169,7 @@ class RoomListViewModel @Inject constructor( is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is RoomListAction.ToggleTag -> handleToggleTag(action) is RoomListAction.ToggleSection -> handleToggleSection(action.section) + is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action) }.exhaustive } @@ -316,6 +320,38 @@ class RoomListViewModel @Inject constructor( } } + private fun handleJoinSuggestedRoom(action: RoomListAction.JoinSuggestedRoom) { + setState { + copy( + suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply { + this[action.roomId] = Loading() + }.toMap() + ) + } + viewModelScope.launch { + try { + awaitCallback { + session.joinRoom(action.roomId, null, action.viaServers ?: emptyList(), it) + } + setState { + copy( + suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply { + this[action.roomId] = Success(Unit) + }.toMap() + ) + } + } catch (failure: Throwable) { + setState { + copy( + suggestedRoomJoiningState = this.suggestedRoomJoiningState.toMutableMap().apply { + this[action.roomId] = Fail(failure) + }.toMap() + ) + } + } + } + } + private fun handleToggleTag(action: RoomListAction.ToggleTag) { session.getRoom(action.roomId)?.let { room -> viewModelScope.launch(Dispatchers.IO) { @@ -342,7 +378,7 @@ class RoomListViewModel @Inject constructor( private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE else -> null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index fa6c970d8a..0c6651dd08 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -16,6 +16,9 @@ package im.vector.app.features.home.room.list +import android.view.View +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Loading import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter @@ -28,6 +31,8 @@ import im.vector.app.features.home.room.typing.TypingHelper import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -50,6 +55,19 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor } } + fun createSuggestion(spaceChildInfo: SpaceChildInfo, + suggestedRoomJoiningStates: Map>, + onJoinClick: View.OnClickListener) : VectorEpoxyModel<*> { + return SuggestedRoomItem_() + .id("sug_${spaceChildInfo.childRoomId}") + .matrixItem(MatrixItem.RoomItem(spaceChildInfo.childRoomId, spaceChildInfo.name, spaceChildInfo.avatarUrl)) + .avatarRenderer(avatarRenderer) + .topic(spaceChildInfo.topic) + .loading(suggestedRoomJoiningStates[spaceChildInfo.childRoomId] is Loading) + .memberCount(spaceChildInfo.activeMemberCount ?: 0) + .buttonClickListener(onJoinClick) + } + private fun createInvitationItem(roomSummary: RoomSummary, changeMembershipState: ChangeMembershipState, listener: RoomListListener?): VectorEpoxyModel<*> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomItem.kt new file mode 100644 index 0000000000..bdd196f750 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SuggestedRoomItem.kt @@ -0,0 +1,117 @@ +/* + * 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.home.room.list + +import android.view.HapticFeedbackConstants +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +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.features.home.AvatarRenderer +import im.vector.app.features.themes.ThemeUtils +import me.gujun.android.span.image +import me.gujun.android.span.span +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass(layout = R.layout.item_suggested_room) +abstract class SuggestedRoomItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + + // Used only for diff calculation + @EpoxyAttribute var topic: String? = null + + @EpoxyAttribute var memberCount: Int = 0 + @EpoxyAttribute var loading: Boolean = false + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var buttonClickListener: View.OnClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.rootView.setOnClickListener(itemClickListener) + holder.rootView.setOnLongClickListener { + it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + itemLongClickListener?.onLongClick(it) ?: false + } + holder.titleView.text = matrixItem.getBestName() + avatarRenderer.render(matrixItem, holder.avatarImageView) + + holder.descriptionText.text = span { + span { + apply { + val tintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) + ContextCompat.getDrawable(holder.view.context, R.drawable.ic_room_profile_member_list) + ?.apply { + ThemeUtils.tintDrawableWithColor(this, tintColor) + }?.let { + image(it) + } + } + +" $memberCount" + apply { + topic?.let { + +" - $topic" + } + } + } + } + + if (loading) { + holder.joinButtonLoading.isVisible = true + holder.joinButton.isInvisible = true + } else { + holder.joinButtonLoading.isVisible = false + holder.joinButton.isVisible = true + } + + holder.joinButton.setOnClickListener { + // local echo + holder.joinButtonLoading.isVisible = true + holder.joinButton.isInvisible = true + buttonClickListener?.onClick(it) + } + } + + override fun unbind(holder: Holder) { + holder.rootView.setOnClickListener(null) + holder.rootView.setOnLongClickListener(null) + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val titleView by bind(R.id.roomNameView) + val joinButton by bind