From eb1fa0919f410ea549370d3bfd8d3eb07534f71b Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 4 Nov 2020 18:08:46 +0100 Subject: [PATCH 001/231] Room member: add methods to get directly from session --- .../org/matrix/android/sdk/rx/RxSession.kt | 8 +++++ .../sdk/api/session/room/RoomService.kt | 17 +++++++++++ .../session/room/DefaultRoomService.kt | 30 +++++++++++++++++++ 3 files changed, 55 insertions(+) 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 03df708c0c..0e5b88adb2 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 @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.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 @@ -92,6 +93,13 @@ class RxSession(private val session: Session) { } } + fun liveRoomMember(userId: String, roomId: String): Observable> { + return session.getRoomMemberLive(userId, roomId).asObservable() + .startWithCallable { + session.getRoomMember(userId, roomId).toOptional() + } + } + fun liveUsers(): Observable> { return session.getUsersLive().asObservable() } 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 b772225f51..f30037e5c2 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 @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.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.util.Cancelable @@ -141,4 +142,20 @@ interface RoomService { * - the power level of the users are not taken into account. Normally in a DM, the 2 members are admins of the room */ fun getExistingDirectRoomWithUser(otherUserId: String): String? + + /** + * Get a room member for the tuple {userId,roomId} + * @param userId the userId to look for. + * @param roomId the roomId to look for. + * @return the room member or null + */ + fun getRoomMember(userId: String, roomId: String): RoomMemberSummary? + + /** + * Observe a live room member for the tuple {userId,roomId} + * @param userId the userId to look for. + * @param roomId the roomId to look for. + * @return a LiveData of the optional found room member + */ + fun getRoomMemberLive(userId: String, roomId: String): LiveData> } 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 d49c2f120c..28656463c1 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 @@ -17,27 +17,37 @@ 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.room.Room 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.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.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 +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource +import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask import org.matrix.android.sdk.internal.session.room.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 internal class DefaultRoomService @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, private val createRoomTask: CreateRoomTask, private val joinRoomTask: JoinRoomTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask, @@ -118,4 +128,24 @@ internal class DefaultRoomService @Inject constructor( override fun getChangeMembershipsLive(): LiveData> { return roomChangeMembershipStateDataSource.getLiveStates() } + + override fun getRoomMember(userId: String, roomId: String): RoomMemberSummary? { + val roomMemberEntity = monarchy.fetchCopied { + RoomMemberHelper(it, roomId).getLastRoomMember(userId) + } + return roomMemberEntity?.asDomain() + } + + override fun getRoomMemberLive(userId: String, roomId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { realm -> + RoomMemberHelper(realm, roomId).queryRoomMembersEvent() + .equalTo(RoomMemberSummaryEntityFields.USER_ID, userId) + }, + { it.asDomain() } + ) + return Transformations.map(liveData) { results -> + results.firstOrNull().toOptional() + } + } } From 51a95554d7b4043e364a986ffe1fb815ad7379fa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Nov 2020 18:30:00 +0100 Subject: [PATCH 002/231] Remove useless code. We can now inject element in the Fragment constructors --- .../java/im/vector/app/core/platform/VectorBaseFragment.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index 179e21a6d8..cd38f5aeaa 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -97,12 +97,9 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { unrecognizedCertificateDialog = screenComponent.unrecognizedCertificateDialog() viewModelFactory = screenComponent.viewModelFactory() childFragmentManager.fragmentFactory = screenComponent.fragmentFactory() - injectWith(injector()) super.onAttach(context) } - protected open fun injectWith(injector: ScreenComponent) = Unit - @CallSuper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) From 4c2e62031afc3fcb3dec7d17313c796900363b91 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Thu, 5 Nov 2020 22:42:31 +0000 Subject: [PATCH 003/231] Translated using Weblate (Catalan) Currently translated at 58.9% (1140 of 1933 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 | 639 ++++++++-------------- 1 file changed, 230 insertions(+), 409 deletions(-) diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index d711d778ca..375133efc0 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -1,36 +1,31 @@ - + - ca ES - Tema clar Tema fosc Tema negre - - S\'està sincronitzant… - Escolta esdeveniments - Notificacions sorolloses + Sincronitzant… + Escoltant esdeveniments + Notificacions amb so Notificacions silencioses - Missatges Sala Configuració Detalls dels participants Historial - Informe d\'errors + Informe d\'error Detalls de la comunitat - D\'acord Cancel·la Desa - Abandona la sala + Surt Envia Reenvia Suprimeix @@ -45,25 +40,24 @@ Reanomena Informa del contingut Trucada activa - Conferència en curs. -\nUniu-vos hi per %1$s o %2$s. + Videoconferència en curs. +\nUneix-te amb %1$s o %2$s Veu Vídeo - La trucada no es pot iniciar, prova-ho més tard - Pot ser que algunes funcions no apareguin per manca de permisos… - Es necessiten permisos per convidar a iniciar una conferència en aquesta sala + No es pot iniciar la trucada, prova-ho més tard + Pot ser que algunes funcions no apareguin per falta de permisos… + Necessites permís per convidar a l\'inici d\'una videoconferència en aquesta sala No es pot iniciar la trucada - Informació del dispositiu - No es poden fer conferències en sales encriptades + Informació de la sessió + No s\'admeten videoconferències en sales xifrades Envia igualment o Convida Fora de línia - - Desconnecta + Tanca la sessió Trucada de veu - Vídeotrucada + Videotrucada Cerca global Marca-ho tot com a llegit Historial @@ -72,52 +66,44 @@ Tanca S\'ha copiat al porta-retalls Desactiva - Confirmació Avís - Inici Preferits Persones Sales - - Filtrar noms de sales - Filtrar preferits - Filtrar persones - Filtrar per noms de sala - Filtrar per nom de comunitats - + Filtra noms de sala + Filtra preferits + Filtra persones + Filtra noms de sala + Filtra noms de comunitat - Convida + Invitacions Prioritat baixa - Converses Llibreta d\'adreces local - Directori de l\'usuari + Directori d\'usuari Només contactes de Matrix - Cap conversa - No vau donar permís al Element per accedir als contactes locals - Cap resultat - + Sense converses + No has donat permís a Element perquè pugui accedir als teus contactes locals + Sense resultats Sales - Directori de sales - No hi ha cap sala - No hi ha cap sala pública disponible + Directori de sala + No hi ha sales + No hi ha sales públiques disponibles - 1 usuari + %d usuari %d usuaris - Convida Comunitats No hi ha cap grup - Envia els registres Envia els registres de fallada Envia una captura de pantalla @@ -127,14 +113,11 @@ Els registres d\'aquest client s\'enviaran amb aquest informe d\'error per tal de diagnosticar problemes. Aquest informe d\'errors, així com també els registres i la captura de pantalla, no seran visibles de forma pública. Desmarqueu si preferiu enviar només el text: Sembla que esteu prou frustrat per a estar sacsejant el telèfon. Voleu enviar un informe d\'error? En l\'última execució l\'aplicació va fallar. Voleu enviar un informe d\'error? - L\'informe d\'error s\'ha enviat correctament No s\'ha pogut enviar l\'informe d\'error (%s) En curs (%s%%) - Envia a Llegit - Uneix-te a la sala Nom d\'usuari Crear un compte @@ -143,20 +126,16 @@ URL del servidor URL del servidor d\'identitat Cerca - Inicia un xat nou Inicia una trucada de veu Inicia una vídeotrucada - Esteu segur que voleu començar una conversa amb %s? Esteu segur que voleu començar una trucada de veu? Esteu segur que voleu començar una trucada de vídeo? - Envia fitxers Fes una foto o un vídeo Fes una foto Fes un vídeo - Entra Crea un compte @@ -189,7 +168,9 @@ Heu oblidat la contrasenya? Usa les opcions personalitzades del servidor (avançat) Comproveu el correu electrònic per continuar el procés de registre - Registrar-se amb el correu elecrònic i el número de telefon alhora no es podrà fer fins que existeixi l\'api. Es farà el registre amb el número de telefon.\n\nPodreu afegir el correu electrònic en els paràmetres del perfil. + Registrar-se amb correu electrònic i número de telèfon alhora no es podrà fer fins que existeixi l\'api. Només es tindrà en compte el número de telèfon. +\n +\nPots afegir el correu electrònic en la configuració de perfil. Aquest servidor es vol assegurar que no sou un robot Aquest nom d\'usuari ja existeix Servidor: @@ -201,7 +182,6 @@ S\'ha enviat un correu electrònic a %s. Seguiu l\'enllaç que conté i feu clic a sota. No s\'ha pogut verificar l\'adreça del correu electrònic: assegureu-vos que heu fet clic a l\'enllaç del correu electrònic La contrasenya s\'ha reiniciat.\n\nLes sessions de tots els dispositius han estat tancades i no rebreu més notificacions automàtiques. Per tal de reactivar les notificacions, torneu a entrar en cada dispositiu. - La URL ha de començar per http[s]:// No s\'ha pogut iniciar la sessió: error de xarxa @@ -210,7 +190,6 @@ No s\'ha pogut fer el registre No s\'ha pogut fer el registre: s\'ha produït una errada en la propietat del correu electrònic Introduïu una URL vàlida - El nom d\'usuari/contrasenya és invàlid No s\'ha reconegut el testimoni d\'accés JSON mal format @@ -218,35 +197,27 @@ S\'han enviat massa peticions Aquest nom d\'usuari ja està en ús L\'enllaç del correu electrònic que encara no heu fet clic - - Llegit per - - Envia com Original Gran Mitjana Petita - "Voleu cancel·lar la baixada? Voleu cancel·lar la pujada? %d s %1$dm %2$ds - Ahir Avui - Nom de la sala Tema de la sala - Truca Trucada establerta @@ -257,16 +228,13 @@ Vídeotrucada d\'entrada Trucada de veu d\'entrada Trucada en curs… - No s\'està responent a la trucada. Ha fallat la connexió de mitjans No es pot iniciar la càmera s\'ha contestat la trucada des d\'un altre lloc - Fes una foto o un vídeo" No es poden gravar vídeos" - Informació Per poder enviar i desar adjunts, el Element necessita permís d\'accés a la galeria de fotos i vídeos.\n\nA la següent finestra emergent, doneu-li el permís d\'accés i així podreu enviar fitxers des del telefon. @@ -279,97 +247,84 @@ \n \nSi accepteu compartir la vostra agenda de contactes amb aquesta finalitat, si us plau permeteu l\'accés de la següent finestra emergent. Per tal de trobar altres usuaris de Matrix a partir dels seus correus electrònics o dels seus números de telefon, el Element necessita permís d\'accés a l\'agenda de contactes.\n\nPermeteu que Element accedeixi als vostres contactes? - No s\'ha realitzat l\'acció per falta de permisos - Desat Desar a baixades? NO Continua - Elimina Uneix-te Previsualitza Rebutja - - Salta al primer missatge no llegit. - + Salta fins al primer missatge no llegit. - L\'usuari de nom %s t\'ha convidat a unir-te a aquesta sala - La invitació s\'ha enviat a però aquest correu electrònic no està associat a aquest compte %s.\nPotser voleu iniciar una sessió amb un compte diferent o afegir aquest correu electrònic a aquest compte. - Esteu intentant accedir a %s. Voldríeu unir-vos per tal de participar en la discussió? + L\'usuari %s t\'ha convidat a unir-te a aquesta sala + Aquesta invitació s\'ha enviat a %s, que no està associat amb aquest compte. +\nPotser hauries d\'iniciar sessió amb un compte diferent o afegir aquest correu electrònic al teu compte. + Estàs intentant accedir a %s. Vols unir-te per poder participar en la discussió\? una sala Aquesta és una previsualització de la sala. Les interacions de la sala estan deshabilitades. - - Xat nou + Nou xat Afegeix un participant 1 participant - Abandona la sala Esteu segurs que voleu sortir de la sala\? Esteu segur que voleu eliminar %s d\'aques xat? Crea - En línia Fora de línia Inactiu - EINES D\'ADMINISTRACIÓ TRUCADA XATS DIRECTES DISPOSITIUS - Convida Deixa aquesta sala Elimina d\'aquesta sala Veta Treu el vet - Fes-lo usuari normal + Retorna\'l a usuari normal Fes-lo moderador Fes-lo administrador - Amaga tots els missatges d\'aquest usuari - Mostra tots els missatges d\'aquest usuari - ID de l\'usuari, nom o correu electrònic + Ignora + Deixa d\'ignorar + ID d\'usuari, nom o correu electrònic Menciona Mostra la llista de dispositius - No podreu desfer aquest canvi ja que esteu donant a aquest usuari els mateixos permisos que teniu.\nN\'esteu segur? - + No podràs desfer aquest canvi ja que estàs donant a l\'usuari el mateix nivell d\'autoritat que el teu. +\nN\'estàs segur\? "Esteu segur que voleu convidar a %s a aquest xat?" Esteu segur que voleu vetar aquest usuari en aquesta conversa? - Convida per ID CONTACTES LOCALS (%d) DIRECTORI D\'USUARI (%s) Només usuaris de Matrix - Convida un usuari per ID Entreu un o més correus electrònics o IDs de Matrix Correu electrònic o ID de Matrix - Cerca %s està escrivint… %1$s & %2$s estan escrivint… %1$s & %2$s & altres estan escrivint… Envia un missatge xifrat… - Envia un missatge (desxifrat)… - La connexió amb el servidor s\'ha perdut. - Els missatges no s\'han enviat. %1$s o %2$s ara? - Els missatges no s\'han enviat perquè hi ha disposistius desconeguts. %1$s o %2$s ara? + Envia un missatge (no encriptat)… + La connectivitat amb el servidor s\'ha perdut. + Missatges no enviats. %1$s o %2$s ara\? + Missatges no enviats ja que hi ha sessions desconegudes. %1$s o %2$s ara\? Reenvia-ho tot Cancel·la-ho tot Reenvia els missatges no enviats Elimina els missatges no enviats No s\'ha trobat el fitxer - No teniu permís per escriure en aquesta sala - + No tens permís per publicar res en aquesta sala Confia No hi confiïs @@ -382,7 +337,6 @@ El certificat ha canviat respecte aquell en el qual el telefon confia. Això NO ÉS GENS HABITUAL. Es recomana que NO ACCEPTEU el certificat nou. El certificat en el que confiàveu ha canviat per un en el que no confieu. El servidor pot haver renovat el certificat. Contacteu amb l\'administrador del servidor per saber l\'empremta digital esperada. Només accepteu el certificat si l\'administrador del servidor ha publicat una empremta digital que coincideixi amb l\'anterior. - Detalls de la sala Participants @@ -391,15 +345,13 @@ L\'ID és incorrecte. Ha de ser una adreça de correu electrònic o un identificador de Matrix com \'@partlocal:domini\' CONVIDATS S\'HAN UNIT - Motiu per informar d\'aquest contingut - Voleu amagar tots els missatges d\'aquest usuari? - -Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar una estona. + Vols amagar tots els missatges d\'aquest usuari\? +\n +\nTingues en compte que aquesta acció reiniciarà l\'aplicació i pot trigar una estona. Cancel·la la pujada Cancel·la la baixada - Cerca Filtra els participants de la sala @@ -408,7 +360,6 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar MISSATGES PARTICIPANTS FITXERS - UNEIX-TE DIRECTORI @@ -421,30 +372,25 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Uneix-te a la sala Uneix-te a una sala Escriviu un id de sala o un àlies de sala - Navega pel directori S\'està cercant al directori… - Preferit Treu prioritat Xat directe Deixa la conversa Oblida - Afegeix una drecera de la pantalla d\'inici - + Afegeix a la pantalla d\'inici Missatges Configuració Versió Termes i condicions - Avisos de terceres parts + Avisos de tercers Copyright Política de privacitat - - Foto del perfil Nom a mostrar Correu electrònic @@ -453,12 +399,10 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Afegeix un número de telèfon Mostra la informació de l\'aplicació als paràmetres del sistema. Informació de l\'aplicació - So de les notificacions Habilita les notificacions d\'aquest compte Habilita les notificacions d\'aquest dispositiu Encén la pantalla durant 3 segons - Missatges que contenen el meu nom Missatges que contenen el meu nom d\'usuari Missatges en xats entre dos @@ -466,13 +410,11 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Quan em convidin a una sala Invitacions de trucada Missatges enviats per un bot - Inicia en arrencar Sincronització en segon pla Habilita la sincronització en segon pla Temps màxim d\'espera de la petició de sincronització Retard entre cada petició - Versió Versió d\'OLM Termes i condicions @@ -482,7 +424,6 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Esborra la memòria cau Esborra la memòria cau multimèdia Manté els elements multimèdia - Configuració de l\'usuari Notificacions Usuaris ignorats @@ -494,19 +435,16 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Permís dels contactes País de l\'agenda de telèfons Pantalla d\'inici - Destaca les sales amb notificacions sense llegir + Fixa les sales amb notificacions no llegides Destaca les sales amb missatges sense llegir Dispositius Previsualitzacions dels URL en línia Mostra sempre l\'hora a tots els missatges Mostra l\'hora en el format de 12 hores Vibra quan mencionin un usuari - Analítiques - Mode d\'estalvi de dades - Detalls del dispositiu ID Nom @@ -517,22 +455,18 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Autenticació Contrasenya: Tramet - Registrat com a Servidor Servidor d\'identitat - Interfície d\'usuari Llengua Seleccioneu una llengua - Verificació pendent Mireu el correu electrònic feu clic en l\'enllaç que s\'ha enviat. Un cop fet això, feu clic per continuar. No s\'ha pogut verificar l\'adreça de correu electrònic. Comproveu el correu electrònic i feu clic en l\'enllaç que s\'ha enviat. Una vegada fet això, feu clic per continuar. Aquest correu electrònic ja està en ús. No s\'ha trobat aquesta adreça de correu electrònic. Aquest número de telèfon ja està en ús. - Canvia la contrasenya Contrasenya actual Contrasenya nova @@ -542,13 +476,9 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Mostra tots els missatges des de %s? Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar una estona. - Esteu segur que voleu esborrar aquesta notificació? - Esteu segur que voleu esborrar el %1$s %2$s? - Escull un país - País Escolliu un país Número de telèfon @@ -558,30 +488,23 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Introduïu un codi d\'activació S\'ha produït un error mentre es validava el número de telèfon Codi - - - Estil - + Insígnia Tres dies Una setmana Un mes Per sempre - - Foto de la sala Nom de la sala Tema Etiqueta de la sala Etiquetat com a: - Preferit Prioritat baixa Cap - Accés i visibilitat Mostra aquesta sala al directori de sales @@ -589,22 +512,18 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Permisos de lectura de l\'historial de la sala Qui pot llegir l\'historial\? Qui pot accedir a la sala? - Ningú - Només els participants (a partir del moment en què es selecciona aquesta opció) - Només els participants (des de que són convidats) - Només els participants (des de que s\'uneixen a la sala) - + Només participants (a partir del moment en què es seleccioni aquesta opció) + Només participants (des de que són convidats) + Només participants (des de que s\'uneixen a la sala) La sala ha de tenir una adreça per tal d\'unir-s\'hi. - Només les persones convidades - Qualsevol que conegui l\'enllaç de la sala, excepte els convidats - Qualsevol que conegui l\'enllaç de la sala, inclosos els convidats - + Només persones que hagin estat convidades + Qualsevol que tingui l\'enllaç de la sala, a part dels convidats + Qualsevol que tingui l\'enllaç de la sala, inclosos els convidats Usuaris vetats - Avançat ID intern d\'aquesta sala @@ -614,43 +533,33 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Encriptació d\'extrem a extrem L\'encriptació d\'extrem a extrem està acitva Necessiteu desconnectar-vos per poder habilitar l\'encriptació. - Encripta només per a dispositius verificats - No enviïs missatges encriptats en aquesta sala des d\'aquest dispositiu a dispositius no verificats. - + Encripta només a sessions verificades + No enviïs mai, des d\'aquesta sessió, missatges encriptats a sessions no verificades d\'aquesta sala. Aquesta sala no té adreces locals - Adreça nova (per exemple #foo:matrix.org") - - Aquesta sala no pertany a cap comunitat - Nova ID de comunitat (per exemple +foo:matrix.org") + Adreça nova (p.e. #foo:matrix.org) + Aquesta sala no mostra insígnies per a cap comunitat + Nou ID de comunitat (p.e. +foo:matrix.org) l\'ID de comunitat és invalid - \'%s\' no és una ID de comunitat vàlida - - + \'%s\' no és un ID de comunitat vàlid Format d\'àlies invàlid \'%s\' no és un format d\'àlies vàlid - No tindreu especificada una adreça principal per aquesta sala. + No tindràs cap adreça principal especificada per a aquesta sala. Avisos de l\'adreça principal - Estableix com a adreça principal Treu com a adreça principal Copia l\'ID de la sala Copia l\'adreça de la sala - L\'encriptació està activada en aquesta sala. L\'encriptació està desactivada en aquesta sala. Activa l\'encriptació \n(avís: no es podrà tornar a desactivar!) - Directori Tema - %s ha intentat carregar un moment concret de la línia de temps d\'aquesta sala però no l\'ha trobat. - Informació de l\'encriptació d\'extrem a extrem - Informació d\'esdeveniment Id de l\'usuari Clau de la identitat Curve25519 @@ -658,15 +567,13 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Algoritme ID de la sessió Error de desencriptació - Informació del dispositiu que envia Nom del dispositiu Nom - ID del dispositiu + ID de sessió Clau del dispositiu Verificació Empremta digital Ed25519 - Exporta les claus de la sala E2E Exporta les claus de la sala Exporta les claus a un fitxer local @@ -676,35 +583,32 @@ Tingueu en compte que aquesta acció reiniciarà l\'aplicació i que pot trigar Les claus E2E de la sala s\'han desat a \'%s\' Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació. - Importa les claus E2E de la sala Importa les claus de la sala Importa les claus de la sala des d\'un fitxer local Importa Encripta només per a dispositius verificats No enviïs mai missatges encriptats a dispositius no verificats des d\'aquest dispositiu. - NO verificat Verificat Bloquejat - dispositiu desconegut cap - Verifica No verifiquis Bloqueja Deixa de bloquejar - Verifica el dispositiu Per tal de verificar que es pot confiar amb aquest dispositiu, contacteu amb el seu propietari per algun altre mitjà (per exemple en persona o trucant-lo) i pregunteu-li si la clau que veu a les seves preferències d\'usuari coincideix amb la clau següent: Si coincideix, premeu el botó per verificar. Si no coincideix, algú està interceptant aquest dispositiu i probablement voldreu prémer el botó per bloquejar-lo. En un futur aquest procés de verificació serà més sofisticat. Verifica que les claus coincideixen - - La sala conté dispositius desconeguts - Aquesta sala conté dispositius desconeguts que no han estat verificats.\nAixò vol dir que no hi ha cap garantia que aquests dispositius siguin dels usuaris corresponents.\nEs recomana que feu el procés de verificació per a cada dispositiu abans de continuar, tot i que, si ho preferiu, podeu reenviar el missatge sense verificar.\n\nDispositius desconeguts: - + La sala conté sessions desconegudes + Aquesta sala conté sessions desconegudes que no han estat verificades. +\nAixò vol dir que no hi ha garanties de que aquestes sessions pertanyin als usuaris que diuen ser. +\nRecomanem que, abans de continuar, duguis a terme el procés de verificació de cadascuna de les sessions. Però, si ho prefereixes, pots reenviar el missatge sense la verificació. +\n +\nDispositius desconeguts: Escolliu un directori de sale És possible que el servidor no estigui disponible o que estigui sobrecarregat @@ -712,10 +616,8 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.URL del servidor base Totes les sales del servidor %s Totes les sales natives de %s - Busca a l\'historial - Mida de la font Molt petita @@ -725,46 +627,38 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.Molt gran Més gran Enorme - - No teniu permisos per a gestionar ginys en aquesta sala + Necessites permisos per gestionar ginys en aquesta sala Ha fallat la creació del giny Fes conferències amb jitsi Confirmeu que voleu esborrar el giny d\'aquesta sala? - No s\'ha pogut crear el giny. No s\'ha pogut enviar la sol·licitud. El nivell de potència ha de ser un enter positiu. - No us trobeu en aquesta sala. - No teniu el permis per fer això en aquesta sala. + No et trobes en aquesta sala. + No tens permís per fer això en aquesta sala. Falta l\'ID de la sala en la sol·licitud. Falta l\'ID d\'usuari en la sol·licitud. La sala %s no és visible. Afegeix aplicacions de Matrix Utilitza la càmera nativa - El nou dispositiu \'%s\' que heu afegit, sol·licita les claus d\'encriptació. El vostre dispositiu \'%s\' sense verificar, sol·licita les claus d\'encriptació. Inicia la verificació Comparteix sense verificar Ignora la sol·licitut - Avís! Les trucades per a conferències estan en desenvolupament i poden no ser fiables. - Error de comandament Ordre no reconegut: %s - Apagat - Sorollós - - Missatge encriptat - + Amb so + Missatge xifrat Crea Crea una comunitat @@ -772,58 +666,49 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.Exemple ID de la comunitat exemple - Inici Usuaris Sales Sense usuaris - Sales S\'hi ha unit Ha sigut convidat Filtre de participants de grups Filtra els grups de sales - L\'administrador de la comunitat no ha fet una descripció llarga per aquesta comunitat. - - %2$s l\'ha fet fora de la sala %1$s + %2$s t\'ha expulsat de %1$s %2$s l\'ha expulsat de la sala %1$s Raó: %1$s Tornar-hi a entrar Oblida la sala Carregant… - Surt Comunitats - Llista de grups - - Tots els missatges (sorollós) + Tots els missatges (amb so) Tots els missatges Només mencions Silencia Notificacions Sacseja el dispositiu amb ràbia per a informar d\'un error - Accions Llista de membres Sincronitzant… - 1 membre actiu + %d membre actiu %d membres actius - 1 membre + %d membre %d membres - 1 nou missatge - %d nous missatges + %d missatge nou + %d missatges nous - - 1 sala + %d sala %d sales @@ -831,93 +716,76 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.%1$s sales trobades per %2$s - 1 sala + %d sala %d sales Obre la capçalera - 1 missatge de notificació sense llegir - %d missatges de notificació sense llegir + %d missatge notificat no llegit + %d missatges notificats no llegits 1 canvi de membres %d canvis de membres - - 1 missatge de notificació sense llegir - %d missatges de notificació sense llegir + %d missatge notificat no llegit + %d missatges notificats no llegits %1$s a %2$s - - 1 complement actiu - %d complements actius + %d giny actiu + %d ginys actius - Envia un adhesiu - Envia un adhesiu Llicències de tercers - - Descarregar - Parlar - Netejar + Descarrega + Parla + Neteja Enviar veu - seguir amb… Ho sento, no s\'ha trobat cap aplicació externa per completar l\'acció. - Tornar a demanar les claus d\'encriptació als teus altres dispositius. - Petició de clau enviada. - Sol·licitud enviada Si us plau, engega Element a un altre dispositiu que pugui desencriptar el missatge de manera que pugui enviar la clau a aquest dispositiu. - Normal Tema Status.im - - Manquen permisos per a dur a terme aquesta acció. + No es pot dur a terme aquesta acció per falta de permisos. Error - Alertes de sistema - Si és possible, escriviu si us plau la descripció en anglès. Actualment no teniu cap conjunt d\'adhesius activat. En voleu afegir algun? - - 1s + %ds %ds - 1m + %dm %dm - 1h + %dh %dh - 1d + %dd %dd - - Ara %1$s + %1$s, ara %1$s fa %2$s - "%1$s,· " %1$s i %2$s %1$s %2$s - Envieu una resposta encriptada… - Envieu una resposta (sense encriptar)… + Envia una resposta (no encriptada)… - 1 escollit - %d escollits + %d seleccionat + %d seleccionats Versió %s Notificació de privacitat @@ -928,34 +796,26 @@ En voleu afegir algun? • El contingut dels missatges de les notificacions s\'obté de forma segura des del servidor de Matrix • Les notificacions contenen meta dades i dades de missatges • Les notificacions no mostraran el contingut dels missatges - Mostra el contingut multimèdia abans d\'enviar-lo - Desactivar el compte Desactivar el meu compte - Notificació de privacitat El Element pot funcionar en segon pla per gestionar les vostres notificacions de forma segura i privada. Això podria afectar el consum de bateria. Concedir permís Escolliu una altra opció - Envia dades d\'anàlisi Element recopila dades d\'anàlisi anònimes per tal de permetre\'ns millorar l\'aplicació. Si us plau, activeu les dades d\'anàlisi per ajudar-nos a millorar Element. Sí, vull ajudar! - - No sou ara mateix membre de cap comunitat. - + Ara mateix no ets membre de cap comunitat. Creeu una frase de pas per xifrar les claus exportades. Haureu d\'introduir la mateixa frase de pas per poder importar les claus. Crea frase de pas Les frases de pas han de coincidir Escriviu aquí… - Falta un paràmetre necessari. Un paràmetre no és vàlid. Premeu \"Enter\" per enviar el missatge Envia missatges de veu - Mostra l\'acció Veta l\'usuari amb l\'ID proporcionat Permeteu de nou l\'usuari amb l\'ID proporcionat @@ -965,74 +825,57 @@ En voleu afegir algun? Entreu a la sala amb l\'àlies donat Sortir de la sala Definir el motiu de la sala - Fer fora l\'usuari amb l\'ID proporcionat + Expulsa l\'usuari amb l\'ID proporcionat Canvia l\'àlies que es mostra Activa/Desactiva el markdown Arreglar la gestió de les Apps de Matrix - - 1 membre + %d membre %d membres - - 1 sala + %d sala %d sales Imatge de perfil - Per poder continuar usant el servidor %1$s heu de revisar i acceptar les clàusules i condicions. Revisa ara - Desactiva el compte - Això farà que no pugueu usar més el vostre compte. No podreu iniciar la sessió i ningú podrà donar-se d\'alta amb el mateix identificador d\'usuari. Això provocarà que el vostre compte abandoni totes les sales a les que estigui participant i eliminarà les vostres dades del compte que hi hagi al vostre servidor d\'identitats. Aquesta acció és irreversible. + Això farà que no puguis utilitzar més el teu compte. No podràs iniciar sessió i ningú podrà tornar-se a registrar amb el mateix ID d\'usuari. Això farà que el teu compte surti de totes les sales a les que estigui participant i eliminarà les dades del compte del teu servidor d\'identitat. Aquesta acció és irreversible. \n -\nDesactivar el compte no implica que s\'eliminin els missatges que heu enviat. Si voleu eliminar-los, marqueu la casella a continuació. +\nDesactivar el compte no implica que s\'oblidin els missatges que has enviat. Si vols que ens n\'oblidem, marca la casella a continuació. \n -\nLa visibilitat dels missatges a Matrix és similar a la del correu electrònic. Que eliminem els vostres missatges significa que els missatges que hagueu enviat no seran accessibles per a nous usuaris o usuaris no registrats, però els usuaris registrats que ja hi tinguin accés, en conservaran una còpia. +\nLa visibilitat dels missatges a Matrix és similar a la del correu electrònic. Que oblidem els teus missatges vol dir que els missatges que hagis enviat no seran accessibles per a nous usuaris o usuaris no registrats, però els usuaris registrats que ja hi tinguin accés, en conservaran una còpia. Si us plau elimina tots els missatges que he enviat mentre es desactiva el meu compte (Avís: això provocarà que els usuaris futurs tinguin una vista incompleta de les converses) Per a continuar, si us plau escriviu la vostra contrasenya: Desactivar el compte - Si us plau escriviu la vostra contrasenya. - Aquesta sala s\'ha substituït i ja no es troba activa + Aquesta sala s\'ha substituït i ja no està activa La conversa segueix aquí - Aquesta sala és la continuació d\'una altra conversa + Aquesta sala és un continuació d\'una altra conversa Feu clic aquí per veure els missatges antics - S\'ha sobrepassat el límit de recursos Contacta amb l\'administrador - Contacteu amb l\'administrador del servei - Aquest servidor base ha sobrepassat un dels seus límits de recursos, així que alguns usuaris no podran identificar-s\'hi. Aquest servidor base ha sobrepassat un dels seus límits de recursos. - Aquest servidor base ha assolit el seu límit màxim mensual d\'activitat d\'usuaris i alguns usuaris no podran identificar-s\'hi. Aquest servidor base ha assolit el seu límit mensual d\'activitat d\'usuaris. - Si us plau %s per tal d\'incrementar aquest límit. "Si us plau %s per continuar usant aquest servei." - - Torna a carregar els membres de la sala - Milloreu el rendiment carregant només els membres de la sala a primera vista. - El vostre servidor base encara no suporta la càrrega en diferit de membres d\'una sala. Proveu-ho més tard. - + Carrega en diferit els participants de la sala + Millora el rendiment carregant només els participants de la sala a primera vista. + El teu servidor encara no és compatible amb la càrrega en diferit dels participants d\'una sala. Prova-ho més tard. Ho sentim, s\'ha produït un error - desplega plega - - Acceptar - + Accepta Trucada Useu el to de Element per defecte per les trucades entrants To de trucada entrant Escolliu el to per les trucades: - - Expulsar + Expulsa Motiu - Mostra la vista prèvia dels enllaços dins del xat en cas que el vostre servidor base suporti aquesta funcionalitat. Envia notificacions d\'escriptura Feu saber a altres usuaris que esteu escrivint. @@ -1040,11 +883,9 @@ En voleu afegir algun? Doneu format a missatges usant la sintaxi Markdown abans d\'enviar-los. Això us permetrà l\'ús de format avançat com ara usar asteriscs per mostrar text en cursiva. No afecta invitacions, expulsions i bloquejos. Mostra els esdeveniments del compte - Truca de totes maneres + Truca igualment Reviseu i accepteu les polítiques d\'aquest servidor base: - Trucada de vídeo en procés… - Executa les proves S\'està executant… (%1$d de %2$d) El diagnòstic bàsic és correcte. Si encara no rebeu notificacions, envieu un informe d\'error per ajudar-nos a investigar. @@ -1054,28 +895,22 @@ En voleu afegir algun? Les notificacions són inhabilitades als paràmetres del sistema. Comproveu els paràmetres del sistema. Obre els paràmetres - Paràmetres del compte. Les notificacions són habilitades per al vostre compte. Les notificacions són inhabilitades per al vostre compte. Comproveu els paràmetres del compte. Habilita - Paràmetres del dispositiu. Les notificacions són habilitades per a aquest dispositiu. Habilita - Repara els serveis de Google Play - Servei de notificacions El servei de notificacions s\'està executant. El servei de notificacions s\'està executant. Proveu de reiniciar l\'aplicació. Inicia el servei - Inicia\'l a l\'arrencada Inhabilita les restriccions - Optimització de bateria El Element no està afectat per l\'optimització de bateria. Mostra els esdeveniments d\'entrada i sortida @@ -1085,36 +920,28 @@ Proveu de reiniciar l\'aplicació. A la pantalla següent se us demanarà que permeteu al Element executar-se sempre al rerefons, si us plau, accepteu-ho. Contrasenya Informació addicional: %s - S\'ha habilitat el Markdown. S\'ha inhabilitat el Markdown. - Avatar de recepció Avatar de notificació Mostra l\'àrea d\'informació Sempre Per als missatges i errors Només per als errors - %1$s: %1$s: %2$s +%d %d+ No s\'ha trobat cap APK de Google Play Services vàlid. Les notificacions poden no funcionar correctament. - - Còpia de seguretat de la clau - Empra una còpia de seguretat de la clau - + Còpia de seguretat de les claus + Utilitza la còpia de seguretat de les claus Omet Fet - Paràmetres avançats de notificacions Importància de les notificacions per esdeveniment - Diagnostica les notificacions Diagnòstic de la resolució de problemes Ha fallat una o més proves, envieu un informe d\'error per ajudar-nos a investigar-ho. - Les notificacions no són permeses per a aquest dispositiu. Comproveu els paràmetres del Element. Paràmetres personalitzats. @@ -1122,7 +949,6 @@ Comproveu els paràmetres del Element. Algunes notificacions estan inhabilitades als vostres paràmetres personalitzats. No s\'ha pogut carregar les regles personalitzades, torneu-ho a provar. Comproveu els paràmetres - Comprovació dels serveis de Play L\'APK dels serveis de Google Play és disponible i al dia. El Element empra els serveis de Google Play per a lliurar les notificacions, però no sembla que estiguen configurats correctament. @@ -1135,54 +961,41 @@ Comproveu els paràmetres del Element. [%1$s] Aquest error és fora del control del Element i segons Google aquest error indica que aquest dispositiu té massa aplicacions registrades amb FCM. L\'error només ocorre en casos en què hi ha un nombre extrem d\'aplicacions, i no hauria d\'afectar un usuari normal. Afegeix un compte - Registre del testimoni S\'ha registrat correctament el testimoni FCM al servidor base. No s\'ha pogut registrar el testimoni FCM al servidor base. \n%1$s - Reinici automàtic del servei de notificacions El servei s\'ha parat i tornat a iniciar automàticament. No s\'ha pogut iniciar el servei - El servei s\'iniciarà quan s\'iniciï el dispositiu. El servei no s\'iniciarà quan el dispositiu s\'iniciï, per la qual cosa no rebreu notificacions fins que el Element s\'haja obert una vegada. Habilita l\'inici durant l\'arrencada - Comprova les restriccions del rerefons Ignora l\'optimització - - Configura les notificacions sorolloses + Configura les notificacions amb so Configura les notificacions de les trucades Configura les notificacions silencioses Seleccioneu el color de LED, la vibració, so… - - Gestió de claus criptogràfiques Mostra les confirmacions de lectura Feu clic en les confirmacions de lectura per obtenir una llista detallada. Concedeix el permís - S\'ha produït un error en verificar la vostra adreça de correu electrònic. - S\'ha produït un error en verificar el vostre número de telèfon. Gestiona les còpies de seguretat de la clau - Inicia la càmera del sistema en lloc de la pantalla personalitzada de la càmera. Aquesta opció requereix una aplicació de tercers per enregistrar els missatges. - L\'ordre «%s» necessita més paràmetres, o alguns paràmetres no són correctes. Silenciós Introduïu una frase de pas La frase de pas és massa feble - Suprimiu la frase de pas si voleu que el Element generi una clau de recuperació. No hi ha cap sessió de Matrix disponible - No perdeu mai els missatges xifrats - Els missatges en sales xifrades estan assegurats amb xifratge punt a punt. Només tu i els destinaris tenen les claus per a llegir aquests missatges. - -Feu una còpia de seguretat de manera segura per evitar perdre-les. + Els missatges en sales encriptades estan assegurats amb encriptació d\'extrem a extrem. Només tu i el/s destinatari/s tenen les claus per a llegir aquests missatges. +\n +\nFes una còpia de seguretat de les teves claus per evitar perdre\'ls. Estableix la frase de pas Fet Desa la clau de recuperació @@ -1190,7 +1003,6 @@ Feu una còpia de seguretat de manera segura per evitar perdre-les. S\'ha desat la clau de recuperació a «%s». Avís: és possible que calgui suprimir el fitxer si es desinstal·la l\'aplicació. - Feu una còpia Comparteix la clau de recuperació amb… Clau de recuperació @@ -1198,17 +1010,13 @@ Avís: és possible que calgui suprimir el fitxer si es desinstal·la l\'aplicac S\'ha iniciat la còpia de seguretat N\'esteu segur? És possible que perdeu l\'accés als vostres missatges si sortiu de la sessió o perdeu el dispositiu. - S\'està recuperant la versió de la còpia de seguretat… Empreu la vostra frase de pas de recuperació per desblocar el vostre historial de missatges xifrat empreu la clau de recuperació Si no coneixeu la vostra contrasenya de recuperació, podeu %s. - Empreu la vostra clau de recuperació per desblocar el vostre historial de missatges xifrat Introduïu la clau de recuperació - Recuperació de missatges - Heu perdut la vostra clau de recuperació? Podeu establir una nova a les preferències. "[%1$s] Aquest error és fora del control del Element. Pot ocórrer per diferents raons. És possible que funcioni si ho torneu a provar més endavant. També podeu comprovar que el servei de Google Play no està restringit a l\'ús de dades a les preferències del sistema, o que el rellotge del dispositiu marca l\'hora correcta. També pot passar amb ROM personalitzades." @@ -1220,20 +1028,14 @@ Aquest error és fora del control del Element. No hi ha cap compte de Google al Les tasques que l\'aplicació intenta fer estaran restringides agressivament mentre estigui al rerefons, i això pot afectar les notificacions. %1$s Si un usuari deixa un dispositiu sense endollar i immòbil durant un període de temps, amb la pantalla apagada, el dispositiu entra en el mode d\'estalvi d\'energia. Això impedeix les aplicacions d\'accedir a la xarxa i ajorna les seves tasques, sincronitzacions i alarmes estàndard. - - S\'està generant la clau de recuperació emprant una frase de pas. Aquest procés pot trigar uns segons. Les vostres claus de xifratge s\'estan emmagatzemant al rerefons al vostre servidor base. La còpia inicial pot trigar alguns minuts. - - No s\'ha pogut desxifrar la còpia de seguretat amb aquesta frase de pas: verifiqueu que la frase de pas que heu introduït és la correcta. Error de xarxa: comproveu la vostra connectivitat i torneu-ho a provar. - S\'està restaurant la còpia de seguretat: Desbloca l\'historial Introduïu una clau de recuperació No s\'ha pogut desxifrar la còpia de seguretat amb aquesta clau de recuperació: verifiqueu que heu introduït la clau correcta. - S\'ha restaurat la còpia de seguretat %s! S\'ha restaurat una còpia amb %d clau. @@ -1243,24 +1045,16 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men S\'ha afegit %d clau nova a aquest dispositiu. S\'ha afegit %d claus noves a aquest dispositiu. - No s\'ha pogut obtenir la versió de les claus de recuperació més recents (%s). La criptografia de la sessió no és activa - - Restaura des de la còpia de seguretat Suprimeix la còpia de seguretat - S\'ha configurat la còpia de seguretat de la clau correctament per a aquest dispositiu. La còpia de seguretat de la clau no és activa en aquest dispositiu. No s\'està fent còpia de seguretat de les vostres claus en aquest dispositiu. - - S\'està suprimint la còpia de seguretat… No s\'ha pogut suprimir la còpia de seguretat (%s) - Suprimeix la còpia de seguretat - La còpia de seguretat té una signatura d\'un dispositiu desconegut amb ID %s. La còpia de seguretat té una signatura vàlida d\'aquest dispositiu. La còpia de seguretat té una signatura vàlida del dispositiu verificat %s. @@ -1268,28 +1062,23 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men La còpia de seguretat té una signatura no vàlida del dispositiu verificat %s La còpia de seguretat té una signatura no vàlida del dispositiu no verificat %s No s\'ha pogut obtenir la informació de confiança per a la còpia de seguretat (%s). - Voleu suprimir la còpia de les vostres claus de xifratge del servidor? Ja no podreu emprar la vostra clau de recuperació per llegir el vostre historial de missatges xifrats. - - La còpia de seguretat de les claus no ha finalitzat, espereu… - Perdreu els vostres missatges xifrats si sortiu ara - S\'està fent la còpia de seguretat de les claus. Si sortiu ara, perdreu accés als vostres missatges xifrats. - No vull els meus missatges xifrats - S\'està fent una còpia de les claus… - Empra la còpia de la clau - N\'esteu segur? - Fes una còpia - Roman + La còpia de seguretat de les claus no ha finalitzat, espera… + Si tanques sessió ara, perdràs els teus missatges xifrats + S\'està fent la còpia de seguretat de les claus. Si tanques sessió ara, perdràs els teus missatges xifrats. + No vull els meus missatges encriptats + Fent còpia de seguretat de les claus… + Utilitza la còpia de seguretat de les claus + N\'estàs segur\? + Còpia de seguretat + Queda\'t Avorta - - Esteu segurs que voleu sortir de la sessió\? - + Estàs segur que vols tancar la sessió\? Recuperació de missatges xifrats Introduïu un nom d\'usuari. Comenceu a emprar la còpia de la clau (Avançat) Exporta les claus manualment - Assegureu la vostra còpia amb una frase de pas. S\'està creant una còpia de seguretat O, assegureu la vostra còpia amb una clau de recuperació, desant-la en un lloc segur. @@ -1300,35 +1089,27 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Comparteix No perdeu mai els missatges xifrats Comenceu a emprar la còpia de seguretat de les claus - No perdeu mai els missatges xifrats Empra la còpia de seguretat de la clau - Claus de missatges xifrats noves Gestiona en la còpia de la clau - S\'està fent una còpia de seguretat de les claus… - S\'ha fet una còpia de seguretat de totes les claus S\'està fent una còpia de seguretat d\'%d clau… S\'està fent una còpia de seguretat de %d claus… - Versió Algoritme Signatura - He sigut jo Còpia de seguretat nova de la clau - Per evitar la pèrdua d\'accés als vostres missatges encriptats, hauríeu d\'activar la còpia de seguretat encriptada a tots els vostres dispositius. - Perdreu accés als vostres missatges encriptats si no feu una còpia de seguretat de les vostres claus abans de sortir de la sessió. - + Per evitar la pèrdua d\'accés als teus missatges encriptats, hauries d\'activar la còpia de seguretat segura a totes les teves sessions. + Perdràs l\'accés als teus missatges encriptats si no fas una còpia de seguretat de les teves claus abans de tancar la sessió. Es desarà una còpia encriptada de les vostres claus al vostre servidor base. Protegiu la vostra còpia de seguretat amb una contrasenya per tal de mantenir-la segura. \n \nPer màxima seguretat, aquesta contrasenya hauria de ser diferent de la contrasenya del vostre compte. El mode d\'estalvi de dades aplica un filtre específic que evita l\'enviament de notificacions de presència i d\'escriptura. - En cas que oblideu la vostra contrasenya, la vostra clau de recuperació és un últim recurs per recuperar l\'accés als vostres missatges xifrats. \nDeseu aquesta clau de recuperació en un lloc molt segur, com ara un gestor de contrasenyes o una caixa forta Deseu la vostra clau de recuperació en un lloc molt segur, com ara un gestor de contrasenyes o una caixa forta @@ -1339,19 +1120,16 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men S\'ha trobat una còpia de seguretat nova de la clau. \n \nSi no heu configurat el mètode de recuperació nou, un atacant podria estar intentant accedir al vostre compte. Canvieu la contrasenya del vostre compte i configureu-hi un mètode de recuperació nou immediatament a la configuració. - Inicialitzar servei - Ignorar - - Iniciar sessió amb Single Sign-on + Inicialitzant servei + Ignora + Inicia sessió amb la inscripció única (SSO) Aquesta URL no està disponible , si us plau verifiqueu-la El vostre dispositiu està usant una versió obsoleta del protocol de seguretat TLS, vulnerable a atacs. Per a la vostra seguretat no us podreu connectar Envieu un missatge amb Enter La tecla Enter del teclat virtual enviarà un missatge en comptes d\'afegir un salt de línia - Actualitzar la contrasenya La contrasenya no és vàlida Les contrasenyes no coincideixen - Medis Compressió estàndard Escollir @@ -1359,34 +1137,27 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Escollir Resposta no vàlida en descobrir homeservers Usar Config - - Verificar dispositiu - - Marcar com a llegit + Verifica sessió + Marca-ho com a llegit Les app no necessita connectar-se al HomeServer en segon pla, hauria de reduir el consum de bateria Administrador d\'integracions - Reproduir el so de disparador - IP desconeguda - %1$s: 1 missatge + %1$s: %2$d missatge %1$s: %2$d missatges %d notificacion %d notificacions - Nou esdeveniment Sala Missatges nous Nova invitació Jo - ** Error en enviar - Si us plau obriu la sala - - Ho sentim, els dispositius amb SO Android inferior a 5.0 no suporten trucades multi-usuari amb Jitsi - + ** No s\'ha pogut enviar - si us plau, obre la sala + Ho sentim, les videoconferències amb Jitsi no són compatibles amb dispositius antics (dispositius amb Android inferior a 5.0) No heu configurat cap administrador d\'integracions. Un nou dispositiu està sol·licitant claus d\'encriptació. \nNom del dispositiu: %1$s @@ -1396,56 +1167,45 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men \nNom del dispositiu: %1$s \nVist per última vegada: %2$s \nSi no heu iniciat sessió en un altre dispositiu, ignoreu la sol·licitud. - Verificar Compartir Sol·licitud de compartició de clau Ignorar - Ja existeix una còpia de seguretat al vostre HomeServer Sembla que ja heu configurat una còpia de seguretat de claus des d\'un altre dispositiu. Voleu reemplaçar-la amb la que esteu creant\? Reemplaçar Aturar - Comprovant l\'estat de la còpia de seguretat Opcions d\'autocompleció del servidor Element ha detectat una configuració de servidor personalitzat pel domini del seu identificador d\'usuari \"%1$s\": \n%2$s Us heu desconnectat a causa de credencials incorrectes o caducades. - Verificar comparant una cadena de text curta. Per la màxima seguretat us recomanem fer això en persona o usar un altre medi de comunicació confiable. Començar la verificació Sol·licitud de verificació entrant Verificar aquest dispositiu per marcar-lo com a confiable. Confiar en dispositius d\'amistats us dona un alleujament addicional quan useu missatges encriptats end-to-end. Verificant aquest dispositiu el marcareu com a confiable, i també marcareu el vostre dispositiu com a confiable pel vostre company. - Verificar aquest dispositiu confirmant els següents emojis que apareguin a la pantalla del vostre company Verificar aquest dispositiu confirmant els següents números que sortiran a la pantalla del vostre company - Heu rebut una sol·licitud de verificació entrant. Veure sol·licitud Esperant que el vostre company confirmi… - Verificat! Heu verificat aquest dispositiu amb èxit. Els missatges segurs amb aquest usuari estan encriptats end-to-end i no serà possible llegir-los per tercers. Entesos - No surt res\? Encara no tots els clients suporten la verificació interactiva. Useu el mètode de verificació antic. Useu el mètode antic de verificació. - Verificació de clau Sol·licitud cancel·lada L\'altre part ha cancel·lat la verificació. \n%s S\'ha cancel·lat la verificació. \nMotiu: %s - Verificació de dispositiu interactiva Sol·licitud de verificació %s vol verificar el vostre dispositiu - L\'usuari ha cancel·lat la verificació El marge de temps pel procés de verificació ha expirat El dispositiu no coneix la transacció @@ -1457,71 +1217,132 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men La clau no coincideix L\'usuari no coincideix Error desconegut - - Editar Respondre - Tornar-ho a provar Unir-se a una sala per començar usant l\'app. Se t\'ha enviat una invitació Convidat per %s - Esteu al dia! - No teniu més missatges sense llegir + No tens més missatges sense llegir Benvingut a casa! Posar-se al dia dels missatges sense llegir Converses Els vostres missatges directes es mostraran aquí Sales Les vostres sales es mostraran aquí - Reaccions Confirmar M\'agrada Afegir reacció Veure reaccions Reaccions - Esdeveniment eliminat per l\'usuari Esdeveniment moderat per l\'administrador de la sala Última edició per %1$s el %2$s - - Esdeveniment mal format, no es pot mostrar - Crear sala nova + Crea sala nova No hi ha xarxa. Si us plau comproveu la vostra connexió a internet. Canviar Canviar de xarxa Espereu, si us plau… Totes les comunitats - Aquesta sala no es pot pre-visualitzar - Element encara no suporta la pre-visualització de sales llegibles per tothom - + Element encara no admet la pre-visualització de les sales llegibles per tothom Sales Missatges directes - Sala nova CREAR - Nom de la sala + Nom Públic Qualsevol podrà unir-se a aquesta sala Directori de sales Publicar aquesta sala al directori de sales - Hi ha hagut un error rebent informació de confança Hi ha hagut un error rebent dades de la còpia de seguretat de les claus - Importar claus e2e des del fitxer \"%1$s\". - Versió de l\'SDK de Matrix Ja esteu veient aquesta sala! - Reaccions ràpides - General Preferències Seguretat i privadesa Expert - + Motiu de l\'expulsió + Expulsa usuari + Aquesta operació encara no està disponible en comptes que utilitzen la inscripció única (SSO). + Continua amb SSO + Per a la teva pròpia privadesa, Element només admet l\'enviament del \"hash\" de correus electrònics i números de telèfon. + Només admès en sales xifrades + El xifrat que utilitza aquesta sala no és compatible + No pots fer això des del mòbil + L\'aplicació no ha pogut crear un compte en aquest servidor. +\n +\nVols registrar-te utilitzant un client web\? + L\'aplicació no ha pogut iniciar sessió en aquest servidor. El servidor és compatible amb el/s següent/s tipus d\'inici de sessió: %1$s. +\n +\nVols iniciar sessió utilitzant un client web\? + No pots fer això des d\'Element per a mòbils + La cerca en sales xifrades encara no està disponible. + La sala encara no s\'ha acabat de crear. Vols cancel·lar la seva creació\? + No s\'ha trobat aquesta sala. Assegura\'t que existeixi. + Mostra detalls com per exemple noms de sala i contingut dels missatges. + No hem pogut convidar els usuaris. Comprova els usuaris que vols convidar i torna-ho a provar. + Estàs segur que vols eliminar aquest esdeveniment\? Tingues en compte que, si suprimeixes un nom de sala o es canvia el tema, podria ser que es revertís. + Una vegada activada, l\'encriptació d\'una sala no es pot desactivar. El servidor no pot llegir els missatges enviats en una sala encriptada, només els poden llegir els participants. Habilitar l\'encriptació pot fer que molts bots i enllaços no funcionin correctament. + Una vegada activada, l\'encriptació no es pot desactivar. + Notificacions + Els missatges d\'aquí no estan encriptats d\'extrem a extrem. + Els missatges d\'aquesta sala no estan encriptats d\'extrem a extrem. + Una vegada activada, l\'encriptació no es pot desactivar. + Si canvies la contrasenya es restabliran les claus d\'encriptació d\'extrem a extrem de totes les teves sessions de manera que l\'historial del xat no es podrà llegir. Configura una còpia de seguretat de les claus o exporta les teves claus de sala d\'una altra sessió abans de fer el canvi de contrasenya. + Has fet la sala accessible per a qualsevol que tingui l\'enllaç. + %1$s ha fet la sala accessible per a qualsevol que tingui l\'enllaç. + Silencia + Només mencions + Tots els missatges + Tots els missatges (amb so) + En aquesta sala no hi ha mitjans + En aquesta sala no hi ha fitxers + No s\'ha trobat cap resultat, utilitza Afegeix amb ID de matrix per a cercar al servidor. + La sala ha estat creada però algunes invitacions no s\'han enviat pel motiu següent: +\n +\n%s + No hi ha ginys actius + %1$s s %2$s i %3$s + Si deixes d\'ignorar aquest usuari, tornaràs a veure tots els seus missatges. + Deixa d\'ignorar + Si ignores aquest usuari s\'eliminaran els seus missatges de les sales que compartiu. +\n +\nPots desfer aquest canvi en qualsevol moment a la configuració general. + Ignora usuari + No podràs desfer aquest canvi ja que t\'estàs baixant de rang, si ets l\'últim usuari de la sala amb privilegis, et serà impossible recuperar-los. + No s\'ha configurat cap servidor d\'identitat. + Sense més resultats + Notificacions + Èxit + Copia + Penja + Rebutja + Accepta + Rebutja + No s\'ha pogut eliminar el giny + No s\'ha pogut afegir el giny + No pots iniciar una trucada amb tu mateix, espera que els participants acceptin la invitació + No pots iniciar una trucada amb tu mateix + Les reunions utilitzen les polítiques de seguretat i permisos de Jitsi. Tots els participants que es trobin dins sala veuran una invitació per unir-se mentre la teva reunió estigui en curs. + Inicia una reunió d\'àudio + Inicia una reunió de vídeo + Ja hi ha una videoconferència en curs! + No tens permís per iniciar una trucada + No tens permís per iniciar una trucada en aquesta sala + No tens permís per iniciar una videoconferència + No tens permís per iniciar una videoconferència en aquesta sala + Reinicia + Omet + Atura + Desconnecta + Revoca + Cap + Latn + \ No newline at end of file From 9755dd73ebd406ab66710fefead04790aa0d5769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 4 Nov 2020 14:49:45 +0000 Subject: [PATCH 004/231] Translated using Weblate (Estonian) Currently translated at 100.0% (1933 of 1933 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 73a5a0dff8..152f5f03e2 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -2187,4 +2187,8 @@ Jututoa seadistused Teema Jututoa teema (kui soovid) + Ekspordi rakenduse taustakontrolli andmed + Otsevestlus + Lisa kaasa võtmevahetusega seotud päringute ajalugu + Rohkem otsingutulemusi pole \ No newline at end of file From eaa7fb71002de7cad0776b2f8e39e8d7521788e2 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Thu, 5 Nov 2020 22:44:39 +0000 Subject: [PATCH 005/231] Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.7% (1929 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 55e904bc2e..674ce19759 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -361,7 +361,7 @@ Digite o ide ou o apelido de uma sala Pesquisar na lista pública - Buscando no diretório… + Buscando na lista… Favoritar Despriorizar @@ -516,7 +516,7 @@ Ativar criptografia· \n(atenção: não é possível desativar depois!) - Diretório + Lista %s tentou carregar um trecho específico da conversa desta sala, mas não conseguiu. @@ -573,15 +573,15 @@ Escolha uma lista pública de salas O servidor pode estar indisponível ou sobrecarregado - Entre com um servidor principal (homeserver) a partir do qual serão listadas as salas públicas + Digite um servidor local, a partir do qual serão listadas as salas públicas Endereço do servidor principal Todas as salas com o servidor %s Todas as salas nativas em %s Pesquisar no histórico - Desconectado + Offline Lista de usuários - LISTA DE USUÁRIAS(OS) (%s) + LISTA DE USUÁRIOS (%s) Iniciar com o sistema Esvaziar o cache de mídia Manter mídia @@ -1407,7 +1407,7 @@ Público Qualquer pessoa poderá entrar nesta sala Lista de Salas - Publicar esta sala na lista das salas + Publicar esta sala na lista de salas Ocorreu um erro ao receber informações de confiança Ocorreu um erro ao obter dados de backup de chaves Importar as chaves de arquivo \"%1$s\". @@ -1452,7 +1452,7 @@ Não consegue encontrar o que você está procurando\? Criar uma sala nova Enviar nova mensagem - Veja lista das salas + Veja lista de salas Nome ou ID (#example:matrix.org) Ativar o recurso de deslizar para responder nas conversas Adicione uma aba dedicada para notificações não lidas na tela principal. From 2a22d71e7cada85cfc0f43ec2f7bed8f80ad388a Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 5 Nov 2020 02:21:22 +0000 Subject: [PATCH 006/231] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (1933 of 1933 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 6dc811aa28..8d019a7635 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2149,4 +2149,8 @@ 主題 聊天室主題(選擇性) 聊天是名稱 + 匯出審核 + 直接訊息 + 傳送金鑰共享請求歷史 + 沒有更多結果 \ No newline at end of file From cb49c7d060a17be23c7233e03b623e56dc311965 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Thu, 5 Nov 2020 22:33:56 +0000 Subject: [PATCH 007/231] Translated using Weblate (Catalan) Currently translated at 100.0% (190 of 190 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/ca/ --- .../src/main/res/values-ca/strings.xml | 234 ++++++++++++++---- 1 file changed, 186 insertions(+), 48 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-ca/strings.xml b/matrix-sdk-android/src/main/res/values-ca/strings.xml index 2dc2206c8c..8ba8c9acfd 100644 --- a/matrix-sdk-android/src/main/res/values-ca/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ca/strings.xml @@ -1,79 +1,217 @@ - + %1$s: %2$s %1$s ha enviat una imatge. - - %1s ha sortit - %1s ha entrat + %1$s ha marxat de la sala + %1$s s\'ha unit a la sala Número de telèfon - Correu electrònic - Missatge encriptat - - la invitació de %s + Missatge xifrat + invitació de %s %1$s ha convidat a %2$s - %1$s us ha convidat + %1$s t\'ha convidat %1$s ha rebutjat la invitació - %1$s ha fet fora a %2$s - - - %1$s ha canviat el seu nom visible de %2$s a %3$s - %1$s ha eliminat el seu nom visible (%2$s) + %1$s ha expulsat %2$s + %1$s ha canviat el seu nom de visualització de %2$s a %3$s + %1$s ha eliminat el seu nom de visualització (era %2$s) %1$s ha canviat el tema a: %2$s %1$s ha canviat el nom de la sala a: %2$s - %s ha contestat la trucada. + %s ha respost a la trucada. %s ha finalitzat la trucada. - tots el membres de la sala, des del punt en què són convidats. - tots els membres de la sala. + tots el participants de la sala, des de que són convidats. + tots els participants de la sala. desconegut (%s). - %1$s ha activat l\'encriptació d\'extrem a extrem (%2$s) - + %1$s ha activat el xifrat d\'extrem a extrem (%2$s) %1$s ha sol·licitat una conferència VoIP - %1$s ha readmès a %2$s - %1$s ha vetat a %2$s + %1$s ha tret el veto a %2$s + %1$s ha vetat %2$s %1$s ha retirat la invitació de %2$s %1$s ha canviat el seu avatar - %1$s ha permès a %2$s veure l\'historial que es generi a partir d\'ara - tots els membres de la sala, des del punt en què hi entrin. + %1$s ha establert la visibilitat de l\'historial futur de la sala a %2$s + tots els participants de la sala, des de que s\'hi uneixen. qualsevol. S\'ha iniciat la conferència VoIP - S\'ha finalitzat la conferència de veu IP - - (s\'ha canviat també l\'avatar) + Ha finalitzat la conferència VoIP + (també ha canviat l\'avatar) %1$s ha eliminat el nom de la sala %1$s ha eliminat el tema de la sala %1$s ha actualitzat el seu perfil %2$s - %1$s ha enviat una invitació a %2$s per a entrar a la sala - %1$s ha acceptat la invitació per a %2$s - - ** No s\'ha pogut desencriptar: %s ** + %1$s ha enviat una invitació a %2$s perquè s\'uneixi a la sala + %1$s ha acceptat la invitació de %2$s + ** No s\'ha pogut desxifrar: %s ** El dispositiu del remitent no ens ha enviat les claus per aquest missatge. - No s\'ha pogut redactar No s\'ha pogut enviar el missatge - No s\'ha pogut pujar la imatge - - S\'ha produït un error de xarxa - S\'ha produït un error de Matrix - - Actualment no es pot tornar a entrar a una sala buida. - - %1$s a canviat el seu nom visible a %2$s - %s ha iniciat una trucada de vídeo. - %s ha iniciat una trucada de veu. - + Error de xarxa + Error de Matrix + Ara per ara no és possible tornar a unir-se a una sala buida. + %1$s a canviat el seu nom de visualització a %2$s + %s ha realitzat una videotrucada. + %s ha realitzat una trucada de veu. - Convidat per %s - Convideu a la sala + Invitació de %s + Convida a la sala %1$s i %2$s Sala buida %1$s i 1 altre %1$s i %2$d altres - - %1$s ha enviat un adhesiu. - - + %s s\'ha actualitzat aquí. + Ho has actualitzat aquí. + %s està sol·licitant la verificació de la teva clau, però el teu client no admet la verificació de clau des del xat. Hauràs d\'utilitzar la verificació de claus heretada per fer la verificació. + Has activat el xifrat d\'extrem a extrem (algorisme %1$s no reconegut). + %1$s ha activat el xifrat d\'extrem a extrem (algorisme %2$s no reconegut). + Has activat el xifrat d\'extrem a extrem. + %1$s ha activat el xifrat d\'extrem a extrem. + Has impedit que els convidats es puguin unir a la sala. + %1$s ha impedit que els convidats es puguin unir a la sala. + Has impedit que els convidats es puguin unir a la sala. + %1$s ha impedit que els convidats es puguin unir a la sala. + Has permès que els convidats s\'uneixin aquí. + %1$s ha permès que els convidats s\'uneixin aquí. + Has permès que els convidats s\'uneixin a la sala. + %1$s ha permès que els convidats s\'uneixin a la sala. + Has eliminat l\'adreça principal d\'aquesta sala. + %1$s ha eliminat l\'adreça principal d\'aquesta sala. + Has establert l\'adreça principal d\'aquesta sala a %1$s. + %1$s ha establert l\'adreça principal d\'aquesta sala a %2$s. + Has afegit %1$s i has eliminat %2$s d\'aquesta sala (adreces). + %1$s ha afegit %2$s i ha eliminat %3$s d\'aquesta sala (adreces). + + Has eliminat l\'adreça %1$s d\'aquesta sala. + Has eliminat les adreces %1$s d\'aquesta sala. + + + %1$s ha eliminat l\'adreça %2$s d\'aquesta sala. + %1$s ha eliminat les adreces %3$s d\'aquesta sala. + + + Has afegit l\'adreça %1$s a aquesta sala. + Has afegit les adreces %1$s a aquesta sala. + + + %1$s ha afegit l\'adreça %2$s a aquesta sala. + %1$s ha afegit les adreces %2$s a aquesta sala. + + Has revocat la invitació de %1$s perquè s\'uneixi a la sala. Motiu: %2$s + %1$s ha revocat la invitació de %2$s perquè s\'uneixi a la sala. Motiu: %3$s + Has revocat la invitació de %1$s + %1$s ha revocat la invitació de %2$s + Has revocat la invitació de %1$s perquè s\'uneixi a la sala + %1$s ha revocat la invitació de %2$s perquè s\'uneixi a la sala + Has retirat la invitació de %1$s. Motiu: %2$s + %1$s ha retirat la invitació de %2$s. Motiu: %3$s + Has acceptat la invitació de %1$s. Motiu: %2$s + %1$s ha acceptat la invitació de %2$s. Motiu: %3$s + Has enviat una invitació a %1$s perquè s\'uneixi a la sala. Motiu: %2$s + %1$s ha enviat una invitació a %2$s perquè s\'uneixi a la sala. Motiu: %3$s + Has vetat %1$s. Motiu: %2$s + %1$s ha vetat %2$s. Motiu: %3$s + Has tret el veto a %1$s. Motiu: %2$s + %1$s ha tret el veto a %2$s. Motiu: %3$s + Has vetat %1$s + Has marxat de la sala. Motiu: %1$s + %1$s ha marxat de la sala. Motiu: %2$s + Has marxat de la sala + %1$s ha marxat de la sala + Has marxat de la sala + Has expulsat %1$s + Has expulsat %1$s. Motiu: %2$s + %1$s ha expulsat %2$s. Motiu: %3$s + Has rebutjat la invitació. Motiu: %1$s + %1$s ha rebutjat la invitació. Motiu: %2$s + Has marxat. Motiu: %1$s + %1$s ha marxat. Motiu: %2$s + T\'has unit. Motiu: %1$s + %1$s s\'ha unit. Motiu: %2$s + T\'has unit a la sala. Motiu: %1$s + %1$s s\'ha unit a la sala. Motiu: %2$s + %1$s t\'ha convidat. Motiu: %2$s + Has convidat %1$s. Motiu: %2$s + %1$s ha convidat %2$s. Motiu: %3$s + La teva invitació. Motiu: %1$s + la invitació de %1$s. Motiu: %2$s + Esborra la cua d\'enviament + Enviant missatge… + Sincronització inicial: +\nImportant dades del compte + Sincronització inicial: +\nImportant comunitats + Sincronització inicial: +\nImportant sales que deixat + Sincronització inicial: +\nImportant compte… + Sincronització inicial: +\nImportant xifrat + Sincronització inicial: +\nImportant sales + Sincronització inicial: +\nImportant sales on hi estàs convidat + Sincronització inicial: +\nImportant sales on hi estàs unit + %1$s de %2$s a %3$s + %1$s ha canviat el nivell d\'autoritat de %2$s. + Has canviat el nivell d\'autoritat de %1$s. + Personalitzat + Personalitzat (%1$d) + Predeterminat + Moderador + Administrador + Has modificat el giny %1$s + %1$s ha modificat el giny %2$s + Has eliminat el giny %1$s + %1$s ha eliminat el giny %2$s + Has afegit el giny %1$s + %1$s ha afegit el giny %2$s + Has acceptat la invitació de %1$s + Has convidat a %1$s + %1$s ha convidat a %2$s + Has enviat una invitació a %1$s perquè s\'uneixi a la sala + Has actualitzat el teu perfil %1$s + Missatge eliminat per %1$s [motiu: %2$s] + Missatge eliminat [motiu: %1$s] + Missatge eliminat per %1$s + Missatge eliminat + Has eliminat l\'avatar de la sala + %1$s ha eliminat l\'avatar de la sala + Has eliminat el tema de la sala + Has eliminat el nom de la sala + Has sol·licitat una conferència VoIP + Has actualitzat aquesta sala. + %s ha actualitzat aquesta sala. + Has activat el xifrat d\'extrem a extrem (%1$s) + Has establert la visibilitat dels missatges futurs a %1$s + %1$s ha establert la visibilitat dels missatges futurs a %2$s + Has establert la visibilitat de l\'historial futur de la sala a %1$s + Has finalitzat la trucada. + Has respost a la trucada. + Has enviat dades per configurar la trucada. + %s ha enviat dades per configurar la trucada. + Has realitzat una trucada de veu. + Has realitzat una videotrucada. + Has canviat el nom de la sala a: %1$s + Has canviat l\'avatar de la sala + %1$s ha canviat l\'avatar de la sala + Has canviat el tema a: %1$s + Has eliminat el teu nom de visualització (era %1$s) + Has canviat el teu nom de visualització de %1$s a %2$s + Has canviat el teu nom de visualització a %1$s + Has canviat el teu avatar + Has retirat la invitació de %1$s + Has tret el veto a %1$s + Has rebutjat la invitació + Has creat la discussió + %1$s ha creat la discussió + T\'has unit + %1$s s\'ha unit + T\'has unit a la sala + Has convidat a %1$s + Has creat la sala + %1$s ha creat la sala + La teva invitació + Has enviat un adhesiu. + Has enviat una imatge. + \ No newline at end of file From 428062e1e430980d2a65bf1599154ba4e3173236 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Thu, 5 Nov 2020 01:30:06 +0000 Subject: [PATCH 008/231] Translated using Weblate (Catalan) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ca/ --- .../android/ca/changelogs/40100100.txt | 1 + .../metadata/android/ca/full_description.txt | 30 +++++++++++++++++++ .../metadata/android/ca/short_description.txt | 1 + fastlane/metadata/android/ca/title.txt | 1 + 4 files changed, 33 insertions(+) create mode 100644 fastlane/metadata/android/ca/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/ca/full_description.txt create mode 100644 fastlane/metadata/android/ca/short_description.txt create mode 100644 fastlane/metadata/android/ca/title.txt diff --git a/fastlane/metadata/android/ca/changelogs/40100100.txt b/fastlane/metadata/android/ca/changelogs/40100100.txt new file mode 100644 index 0000000000..70b786d12e --- /dev/null +++ b/fastlane/metadata/android/ca/changelogs/40100100.txt @@ -0,0 +1 @@ +// TODO diff --git a/fastlane/metadata/android/ca/full_description.txt b/fastlane/metadata/android/ca/full_description.txt new file mode 100644 index 0000000000..b45a5488ea --- /dev/null +++ b/fastlane/metadata/android/ca/full_description.txt @@ -0,0 +1,30 @@ +Element és un nou tipus d'aplicació de missatgeria i col·laboració que: + +1. Et dóna a tu el control per preservar la teva privadesa +2. Et permet comunicar-te amb qualsevol persona de la xarxa Matrix i, fins i tot més enllà gràcies a integracions amb altres aplicacions com Slack +3. Et protegeix de la publicitat, l'obtenció no desitjada de dades i dels navegadors amb accés controlat +4. T'assegura a tu mitjançant l'encriptació d'extrem a extrem i amb signatures creuades per verificar els altres + +Element és completament diferent a les altres aplicacions de missatgeria i col·laboració ja que és descentralitzat i de codi obert. + +Element et deixa triar l'allotjament perquè disposis de privadesa, propietat i control de les teves dades i converses. Et dóna accés a una xarxa oberta perquè no et quedis únicament parlant amb els usuaris d'Element. + +Element pot fer tot això ja que opera sobre Matrix - l'estàndard per a les comunicacions obertes i descentralitzades. + +Element et dóna el control perquè et deixa escollir qui vols que allotgi les teves converses. Des de l'aplicació d'Element, pots triar l'allotjament de diferents maneres: + +1. Crea un compte gratuït al servidor públic de matrix.org allotjat pels desenvolupadors de Matrix o tria'n un entre els milers de servidors públics creats per voluntaris +2. Allotja tu mateix el teu compte en el teu propi servidor +3. Registra el compte en un servidor personalitzat subscrivint-te a la plataforma d'Element Matrix Services (EMS) + +Per què escollir Element? + +PROPIETAT DE LES TEVES DADES: Tu decideixes a on desar les teves dades i missatges. Tu les controles i n'ets el propietari, no una mega-corporació que s'aprofita de les teves dades o les cedeix a tercers. + +MISSATGERIA I COL·LABORACIÓ OBERTA: Pots parlar amb qualsevol que estigui a la xarxa Matrix, ja sigui amb Element o amb qualsevol altre aplicació Matrix, fins i tot encara que utilitzin sistemes de missatgeria diferents com Slack, IRC o XMPP. + +SUPER-SEGUR: Encriptació d'extrem a extrem real (només qui està conversant pot desxifrar els missatges), i amb signatures creuades per a verificar els dispositius dels participants en les converses. + +COMUNICACIÓ COMPLETA: Missatgeria, veu i video-trucades, compartició de fitxers, compartició de pantalla i un munt d'integracions, bots i ginys. Crea sales, comunitats, mantén-te en contacte i enllesteix el que et proposes. + +A TOT ARREU: Mantingues el contacte des de qualsevol lloc on siguis, amb un historial de missatges totalment sincronitzat entre tots els teus dispositius i també a la web: https://app.element.io. diff --git a/fastlane/metadata/android/ca/short_description.txt b/fastlane/metadata/android/ca/short_description.txt new file mode 100644 index 0000000000..1e842ec64e --- /dev/null +++ b/fastlane/metadata/android/ca/short_description.txt @@ -0,0 +1 @@ +Xat i VoIP segurs i descentralitzats. Protegeix les teves dades de tercers. diff --git a/fastlane/metadata/android/ca/title.txt b/fastlane/metadata/android/ca/title.txt new file mode 100644 index 0000000000..adc831006a --- /dev/null +++ b/fastlane/metadata/android/ca/title.txt @@ -0,0 +1 @@ +Element (anteriorment Riot.im) From ffd3b9a7a73c8a7c4c54b0d9cafa2518573b388c Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 5 Nov 2020 02:47:20 +0000 Subject: [PATCH 009/231] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- .../android/zh_Hant/changelogs/40100100.txt | 1 + .../android/zh_Hant/full_description.txt | 30 +++++++++++++++++++ .../android/zh_Hant/short_description.txt | 1 + fastlane/metadata/android/zh_Hant/title.txt | 1 + 4 files changed, 33 insertions(+) create mode 100644 fastlane/metadata/android/zh_Hant/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/zh_Hant/full_description.txt create mode 100644 fastlane/metadata/android/zh_Hant/short_description.txt create mode 100644 fastlane/metadata/android/zh_Hant/title.txt diff --git a/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt b/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt new file mode 100644 index 0000000000..3c21bcbeb6 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant/changelogs/40100100.txt @@ -0,0 +1 @@ +// 待辦事項 diff --git a/fastlane/metadata/android/zh_Hant/full_description.txt b/fastlane/metadata/android/zh_Hant/full_description.txt new file mode 100644 index 0000000000..2fdf6fa478 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant/full_description.txt @@ -0,0 +1,30 @@ +Element 是一種新型態的即時通訊軟體與協作應用程式: + +1. 自己的隱私自己掌控 +2. 讓您與任何在 Matrix 網路中的人通訊,甚至可與如 Slack 等的應用程式整合 +3. 保護您免受廣告、資料採礦與圍牆花園的侵害 +4. 透過端到端加密保護您,並使用交叉簽章來驗證其他人 + +Element 是去中心化且開放原始碼的應用程式,因此與其他即時通訊與協作軟體完全不同。 + +Element 讓您可以自架(或是自行選擇服務提供者)所以您擁有您資料與對話的隱私、所有權與控制權。它讓您可以存取開放的網路;因此,您不僅可以與其他 Matrix 使用者聊天。而且非常安全。 + +Element 能作到這些事情是因為它在 Matrix 上執行,這是一個開放的去中心化通訊的標準。 + +Element 讓您選擇您要在哪裡託管您的對話來將控制權還給您。在 Element 應用程式中,您可以選擇其他方式來託管: + +1. 在由 Matrix 開發者架設的 matrix.org 公開伺服器上取得免費的帳號,或是從數千個由志願者所架設的公開伺服器中選擇 +2. 在您自己的硬體上自行架設伺服器並建立帳號 +3. 訂閱 Element Matrix 服務託管平台並在自訂伺服氣上註冊帳號 + +為何選擇 Element? + +擁有您的資料:您決定您的資料與訊息要放在哪裡。您擁有並控制它,而非某些科技巨頭會挖掘您的資料並將其售予第三方。 + +開放的即時通訊與協作:您可以與 Matrix 網路中的任何人聊天,不管他們是使用 Element 或其他 Matrix 應用程式都可以,或甚至是其他的訊息系統,如 Slack、IRC 或 XMPP 也都可以。 + +超級安全:即時的端到端加密(僅有參與對話的人可以解密訊息),以及交叉簽章以驗證對話參與者的裝置。 + +完整通訊:即時通訊、語音與視訊通話、檔案分享、畫面分享與超多的整合、機器人與小工具。建立聊天室、保持聯繫並完成工作。 + +無論您身在何處:無論您身在何處,都可以透過 https://app.element.io 來在所有裝置與網路上保持訊息歷史同步。 diff --git a/fastlane/metadata/android/zh_Hant/short_description.txt b/fastlane/metadata/android/zh_Hant/short_description.txt new file mode 100644 index 0000000000..23bb82c04e --- /dev/null +++ b/fastlane/metadata/android/zh_Hant/short_description.txt @@ -0,0 +1 @@ +安全的去中心化聊天與 VoIP。確保您的資料不受第三方的影響。 diff --git a/fastlane/metadata/android/zh_Hant/title.txt b/fastlane/metadata/android/zh_Hant/title.txt new file mode 100644 index 0000000000..3be2260b73 --- /dev/null +++ b/fastlane/metadata/android/zh_Hant/title.txt @@ -0,0 +1 @@ +Element(曾名為 Riot.im) From 6dfdc77ebd0ef07f34f62c6cb0f6dd5b9ab6e0ee Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Nov 2020 14:41:43 +0100 Subject: [PATCH 010/231] Use room member instead of user when it makes sense --- .../app/features/call/VectorCallViewModel.kt | 2 +- .../call/WebRtcPeerConnectionManager.kt | 15 +-- .../call/conference/JitsiCallViewModel.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 9 +- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../home/room/detail/RoomDetailViewState.kt | 2 +- .../detail/search/SearchResultController.kt | 2 +- .../action/MessageActionsViewModel.kt | 11 ++- .../timeline/factory/MessageItemFactory.kt | 43 +++++---- .../app/features/html/EventHtmlRenderer.kt | 46 ++++++---- .../app/features/html/MxLinkTagHandler.kt | 89 ------------------ .../app/features/html/PillsPostProcessor.kt | 91 +++++++++++++++++++ .../app/features/invite/VectorInviteView.kt | 3 +- .../notifications/NotifiableEventResolver.kt | 2 +- .../NotificationBroadcastReceiver.kt | 2 +- 15 files changed, 179 insertions(+), 142 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/html/MxLinkTagHandler.kt create mode 100644 vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt index 445f40e5b1..bd16adf3e7 100644 --- a/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/VectorCallViewModel.kt @@ -137,7 +137,7 @@ class VectorCallViewModel @AssistedInject constructor( session.callSignalingService().getCallWithId(it)?.let { mxCall -> this.call = mxCall mxCall.otherUserId - val item: MatrixItem? = session.getUser(mxCall.otherUserId)?.toMatrixItem() + val item: MatrixItem? = session.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem() mxCall.addListener(callStateListener) diff --git a/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt index 86b38c1158..5bc87ed1d1 100644 --- a/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt +++ b/vector/src/main/java/im/vector/app/features/call/WebRtcPeerConnectionManager.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent +import org.matrix.android.sdk.api.util.toMatrixItem import org.webrtc.AudioSource import org.webrtc.AudioTrack import org.webrtc.Camera1Enumerator @@ -330,8 +331,8 @@ class WebRtcPeerConnectionManager @Inject constructor( currentCall?.mxCall ?.takeIf { it.state is CallState.Connected } ?.let { mxCall -> - val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() - ?: mxCall.roomId + val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName() + ?: mxCall.otherUserId // Start background service with notification CallService.onPendingCall( context = context, @@ -388,7 +389,7 @@ class WebRtcPeerConnectionManager @Inject constructor( val mxCall = callContext.mxCall // Update service state - val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() + val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName() ?: mxCall.roomId CallService.onPendingCall( context = context, @@ -576,7 +577,7 @@ class WebRtcPeerConnectionManager @Inject constructor( ?.let { mxCall -> // Start background service with notification - val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() + val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName() ?: mxCall.otherUserId CallService.onOnGoingCallBackground( context = context, @@ -650,7 +651,7 @@ class WebRtcPeerConnectionManager @Inject constructor( callAudioManager.startForCall(createdCall) currentCall = callContext - val name = currentSession?.getUser(createdCall.otherUserId)?.getBestName() + val name = currentSession?.getRoomMember(createdCall.otherUserId, createdCall.roomId)?.toMatrixItem()?.getBestName() ?: createdCall.otherUserId CallService.onOutgoingCallRinging( context = context.applicationContext, @@ -706,7 +707,7 @@ class WebRtcPeerConnectionManager @Inject constructor( } // Start background service with notification - val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() + val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName() ?: mxCall.otherUserId CallService.onIncomingCallRinging( context = context, @@ -845,7 +846,7 @@ class WebRtcPeerConnectionManager @Inject constructor( } val mxCall = call.mxCall // Update service state - val name = currentSession?.getUser(mxCall.otherUserId)?.getBestName() + val name = currentSession?.getRoomMember(mxCall.otherUserId, mxCall.roomId)?.toMatrixItem()?.getBestName() ?: mxCall.otherUserId CallService.onPendingCall( context = context, diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt index cf4d1417e3..783b519706 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiCallViewModel.kt @@ -46,7 +46,7 @@ class JitsiCallViewModel @AssistedInject constructor( } init { - val me = session.getUser(session.myUserId)?.toMatrixItem() + val me = session.getRoomMember(session.myUserId, args.roomId)?.toMatrixItem() val userInfo = JitsiMeetUserInfo().apply { displayName = me?.getBestName() avatar = me?.avatarUrl?.let { session.contentUrlResolver().resolveFullSize(it) }?.let { URL(it) } 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 9c6c473a7f..e1881b5e49 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 @@ -141,6 +141,7 @@ import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsB import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan +import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer @@ -221,7 +222,8 @@ class RoomDetailFragment @Inject constructor( private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager, private val matrixItemColorProvider: MatrixItemColorProvider, private val imageContentRenderer: ImageContentRenderer, - private val roomDetailPendingActionStore: RoomDetailPendingActionStore + private val roomDetailPendingActionStore: RoomDetailPendingActionStore, + private val pillsPostProcessorFactory: PillsPostProcessor.Factory ) : VectorBaseFragment(), TimelineEventController.Callback, @@ -254,6 +256,9 @@ class RoomDetailFragment @Inject constructor( private val glideRequests by lazy { GlideApp.with(this) } + private val pillsPostProcessor by lazy { + pillsPostProcessorFactory.create(roomDetailArgs.roomId) + } private val autoCompleter: AutoCompleter by lazy { autoCompleterFactory.create(roomDetailArgs.roomId) @@ -848,7 +853,7 @@ class RoomDetailFragment @Inject constructor( if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { val parser = Parser.builder().build() val document = parser.parse(messageContent.formattedBody ?: messageContent.body) - formattedBody = eventHtmlRenderer.render(document) + formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) } composerLayout.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) 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 9efad1081f..04677e7c58 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 @@ -1300,7 +1300,7 @@ class RoomDetailViewModel @AssistedInject constructor( } if (summary.membership == Membership.INVITE) { summary.inviterId?.let { inviterId -> - session.getUser(inviterId) + session.getRoomMember(inviterId, summary.roomId) }?.also { setState { copy(asyncInviter = Success(it)) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index b31c972d1a..d7d084f6b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -60,7 +60,7 @@ data class RoomDetailViewState( val roomId: String, val eventId: String?, val myRoomMember: Async = Uninitialized, - val asyncInviter: Async = Uninitialized, + val asyncInviter: Async = Uninitialized, val asyncRoomSummary: Async = Uninitialized, val activeRoomWidgets: Async> = Uninitialized, val typingMessage: String? = null, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index b927fb5ff3..f661aa5ba9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -123,7 +123,7 @@ class SearchResultController @Inject constructor( .formattedDate(dateFormatter.format(event.originServerTs, DateFormatKind.MESSAGE_SIMPLE)) .spannable(spannable) .sender(eventAndSender.sender - ?: eventAndSender.event.senderId?.let { session.getUser(it) }?.toMatrixItem()) + ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem()) .listener { listener?.onItemClicked(eventAndSender.event) } .let { result.add(it) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 0d1e2261cd..835cecf829 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -28,6 +28,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.html.EventHtmlRenderer +import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.reactions.data.EmojiDataSource @@ -57,18 +58,22 @@ import java.util.ArrayList * Information related to an event and used to display preview in contextual bottom sheet. */ class MessageActionsViewModel @AssistedInject constructor(@Assisted - initialState: MessageActionState, + private val initialState: MessageActionState, private val eventHtmlRenderer: Lazy, private val htmlCompressor: VectorHtmlCompressor, private val session: Session, private val noticeEventFormatter: NoticeEventFormatter, private val stringProvider: StringProvider, + private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { private val eventId = initialState.eventId private val informationData = initialState.informationData private val room = session.getRoom(initialState.roomId) + private val pillsPostProcessor by lazy { + pillsPostProcessorFactory.create(initialState.roomId) + } @AssistedInject.Factory interface Factory { @@ -164,7 +169,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted return when (timelineEvent.root.getClearType()) { EventType.MESSAGE, - EventType.STICKER -> { + EventType.STICKER -> { val messageContent: MessageContent? = timelineEvent.getLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { val html = messageContent.formattedBody @@ -172,7 +177,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted ?.let { htmlCompressor.compress(it) } ?: messageContent.body - eventHtmlRenderer.get().render(html) + eventHtmlRenderer.get().render(html, pillsPostProcessor) } else if (messageContent is MessageVerificationRequestContent) { stringProvider.getString(R.string.verification_request) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index dd7a87cce6..2b067ccf3f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -60,6 +60,7 @@ import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovement import im.vector.app.features.home.room.detail.timeline.tools.linkify import im.vector.app.features.html.CodeVisitor import im.vector.app.features.html.EventHtmlRenderer +import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.html.VectorHtmlCompressor import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer @@ -106,15 +107,19 @@ class MessageItemFactory @Inject constructor( private val defaultItemFactory: DefaultItemFactory, private val noticeItemFactory: NoticeItemFactory, private val avatarSizeProvider: AvatarSizeProvider, + private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val session: Session) { + private val pillsPostProcessor by lazy { + pillsPostProcessorFactory.create(roomSummaryHolder.roomSummary?.roomId) + } + fun create(event: TimelineEvent, nextEvent: TimelineEvent?, highlight: Boolean, callback: TimelineEventController.Callback? ): VectorEpoxyModel<*>? { event.root.eventId ?: return null - val informationData = messageInformationDataFactory.create(event, nextEvent) if (event.root.isRedacted()) { @@ -139,16 +144,16 @@ class MessageItemFactory @Inject constructor( // val all = event.root.toContent() // val ev = all.toModel() return when (messageContent) { - is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) - is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) - is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) + is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes) + is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) + is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } @@ -159,7 +164,7 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? { return when (messageContent.optionType) { - OPTION_TYPE_POLL -> { + OPTION_TYPE_POLL -> { MessagePollItem_() .attributes(attributes) .callback(callback) @@ -217,13 +222,17 @@ class MessageItemFactory @Inject constructor( attributes: AbsMessageItem.Attributes): VerificationRequestItem? { // If this request is not sent by me or sent to me, we should ignore it in timeline val myUserId = session.myUserId + val roomId = roomSummaryHolder.roomSummary?.roomId if (informationData.senderId != myUserId && messageContent.toUserId != myUserId) { return null } val otherUserId = if (informationData.sentByMe) messageContent.toUserId else informationData.senderId - val otherUserName = if (informationData.sentByMe) session.getUser(messageContent.toUserId)?.displayName - else informationData.memberName + val otherUserName = if (informationData.sentByMe) { + session.getRoomMember(messageContent.toUserId, roomId ?: "")?.displayName + } else { + informationData.memberName + } return VerificationRequestItem_() .attributes( VerificationRequestItem.Attributes( @@ -362,7 +371,7 @@ class MessageItemFactory @Inject constructor( val codeVisitor = CodeVisitor() codeVisitor.visit(localFormattedBody) when (codeVisitor.codeKind) { - CodeVisitor.Kind.BLOCK -> { + CodeVisitor.Kind.BLOCK -> { val codeFormattedBlock = htmlRenderer.get().render(localFormattedBody) if (codeFormattedBlock == null) { buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes) @@ -378,7 +387,7 @@ class MessageItemFactory @Inject constructor( buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes) } } - CodeVisitor.Kind.NONE -> { + CodeVisitor.Kind.NONE -> { buildFormattedTextItem(messageContent, informationData, highlight, callback, attributes) } } @@ -393,7 +402,7 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes): MessageTextItem? { val compressed = htmlCompressor.compress(messageContent.formattedBody!!) - val formattedBody = htmlRenderer.get().render(compressed) + val formattedBody = htmlRenderer.get().render(compressed, pillsPostProcessor) return buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes) } @@ -528,7 +537,7 @@ class MessageItemFactory @Inject constructor( private fun MessageContentWithFormattedBody.getHtmlBody(): CharSequence { return matrixFormattedBody ?.let { htmlCompressor.compress(it) } - ?.let { htmlRenderer.get().render(it) } + ?.let { htmlRenderer.get().render(it, pillsPostProcessor) } ?: body } diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index fa644f41b6..d3b3be6a3e 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -17,21 +17,23 @@ package im.vector.app.features.html import android.content.Context -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.glide.GlideApp +import android.text.Spannable +import androidx.core.text.toSpannable import im.vector.app.core.resources.ColorProvider -import im.vector.app.features.home.AvatarRenderer import io.noties.markwon.Markwon import io.noties.markwon.html.HtmlPlugin -import io.noties.markwon.html.TagHandlerNoOp import org.commonmark.node.Node import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @Singleton -class EventHtmlRenderer @Inject constructor(context: Context, - htmlConfigure: MatrixHtmlPluginConfigure) { +class EventHtmlRenderer @Inject constructor(htmlConfigure: MatrixHtmlPluginConfigure, + context: Context) { + + interface PostProcessor { + fun afterRender(renderedText: Spannable) + } private val markwon = Markwon.builder(context) .usePlugin(HtmlPlugin.create(htmlConfigure)) @@ -41,35 +43,47 @@ class EventHtmlRenderer @Inject constructor(context: Context, return markwon.parse(text) } - fun render(text: String): CharSequence { + /** + * @param text the text you want to render + * @param postProcessors an optional array of post processor to add any span if needed + */ + fun render(text: String, vararg postProcessors: PostProcessor): CharSequence { return try { - markwon.toMarkdown(text) + val parsed = markwon.parse(text) + renderAndProcess(parsed, postProcessors) } catch (failure: Throwable) { Timber.v("Fail to render $text to html") text } } - fun render(node: Node): CharSequence? { + /** + * @param node the node you want to render + * @param postProcessors an optional array of post processor to add any span if needed + */ + fun render(node: Node, vararg postProcessors: PostProcessor): CharSequence? { return try { - markwon.render(node) + renderAndProcess(node, postProcessors) } catch (failure: Throwable) { Timber.v("Fail to render $node to html") return null } } + + private fun renderAndProcess(node: Node, postProcessors: Array): CharSequence { + val renderedText = markwon.render(node).toSpannable() + postProcessors.forEach { + it.afterRender(renderedText) + } + return renderedText + } } -class MatrixHtmlPluginConfigure @Inject constructor(private val context: Context, - private val colorProvider: ColorProvider, - private val avatarRenderer: AvatarRenderer, - private val session: ActiveSessionHolder) : HtmlPlugin.HtmlConfigure { +class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: ColorProvider) : HtmlPlugin.HtmlConfigure { override fun configureHtml(plugin: HtmlPlugin) { plugin - .addHandler(TagHandlerNoOp.create("a")) .addHandler(FontTagHandler()) - .addHandler(MxLinkTagHandler(GlideApp.with(context), context, avatarRenderer, session)) .addHandler(MxReplyTagHandler()) .addHandler(SpanHandler(colorProvider)) } diff --git a/vector/src/main/java/im/vector/app/features/html/MxLinkTagHandler.kt b/vector/src/main/java/im/vector/app/features/html/MxLinkTagHandler.kt deleted file mode 100644 index 368fdd27ff..0000000000 --- a/vector/src/main/java/im/vector/app/features/html/MxLinkTagHandler.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.html - -import android.content.Context -import android.text.style.URLSpan -import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.glide.GlideRequests -import im.vector.app.features.home.AvatarRenderer -import io.noties.markwon.MarkwonVisitor -import io.noties.markwon.SpannableBuilder -import io.noties.markwon.html.HtmlTag -import io.noties.markwon.html.MarkwonHtmlRenderer -import io.noties.markwon.html.tag.LinkHandler -import org.matrix.android.sdk.api.session.permalinks.PermalinkData -import org.matrix.android.sdk.api.session.permalinks.PermalinkParser -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.util.MatrixItem - -class MxLinkTagHandler(private val glideRequests: GlideRequests, - private val context: Context, - private val avatarRenderer: AvatarRenderer, - private val sessionHolder: ActiveSessionHolder) : LinkHandler() { - - override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) { - val link = tag.attributes()["href"] - if (link != null) { - val permalinkData = PermalinkParser.parse(link) - val matrixItem = when (permalinkData) { - is PermalinkData.UserLink -> { - val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId) - MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl) - } - is PermalinkData.RoomLink -> { - if (permalinkData.eventId == null) { - val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias) - if (permalinkData.isRoomAlias) { - MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl) - } else { - MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl) - } - } else { - // Exclude event link (used in reply events, we do not want to pill the "in reply to") - null - } - } - is PermalinkData.GroupLink -> { - val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId) - MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl) - } - else -> null - } - - if (matrixItem == null) { - super.handle(visitor, renderer, tag) - } else { - val span = PillImageSpan(glideRequests, avatarRenderer, context, matrixItem) - SpannableBuilder.setSpans( - visitor.builder(), - span, - tag.start(), - tag.end() - ) - SpannableBuilder.setSpans( - visitor.builder(), - URLSpan(link), - tag.start(), - tag.end() - ) - } - } else { - super.handle(visitor, renderer, tag) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt new file mode 100644 index 0000000000..c13f5fdfb3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.html + +import android.content.Context +import android.text.Spannable +import android.text.Spanned +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.glide.GlideApp +import im.vector.app.features.home.AvatarRenderer +import io.noties.markwon.core.spans.LinkSpan +import org.matrix.android.sdk.api.session.permalinks.PermalinkData +import org.matrix.android.sdk.api.session.permalinks.PermalinkParser +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.toMatrixItem + +class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomId: String?, + private val context: Context, + private val avatarRenderer: AvatarRenderer, + private val sessionHolder: ActiveSessionHolder) + : EventHtmlRenderer.PostProcessor { + + @AssistedInject.Factory + interface Factory { + fun create(roomId: String?): PillsPostProcessor + } + + override fun afterRender(renderedText: Spannable) { + addPillSpans(renderedText, roomId) + } + + private fun addPillSpans(renderedText: Spannable, roomId: String?) { + // We let markdown handle links and then we add PillImageSpan if needed. + val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java) + linkSpans.forEach { linkSpan -> + val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach + val startSpan = renderedText.getSpanStart(linkSpan) + val endSpan = renderedText.getSpanEnd(linkSpan) + renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? { + val permalinkData = PermalinkParser.parse(url) + val matrixItem = when (permalinkData) { + is PermalinkData.UserLink -> { + if (roomId == null) { + sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)?.toMatrixItem() + } else { + sessionHolder.getSafeActiveSession()?.getRoomMember(permalinkData.userId, roomId)?.toMatrixItem() + } + } + is PermalinkData.RoomLink -> { + if (permalinkData.eventId == null) { + val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias) + if (permalinkData.isRoomAlias) { + MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl) + } else { + MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl) + } + } else { + // Exclude event link (used in reply events, we do not want to pill the "in reply to") + null + } + } + is PermalinkData.GroupLink -> { + val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId) + MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl) + } + else -> null + } ?: return null + return PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem) + } +} diff --git a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt index 881c446eb2..f238261057 100644 --- a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt +++ b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt @@ -27,6 +27,7 @@ import im.vector.app.core.platform.ButtonStateView import im.vector.app.features.home.AvatarRenderer import kotlinx.android.synthetic.main.vector_invite_view.view.* import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -73,7 +74,7 @@ class VectorInviteView @JvmOverloads constructor(context: Context, attrs: Attrib } } - fun render(sender: User, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) { + fun render(sender: RoomMemberSummary, mode: Mode = Mode.LARGE, changeMembershipState: ChangeMembershipState) { if (mode == Mode.LARGE) { updateLayoutParams { height = LayoutParams.MATCH_CONSTRAINT } avatarRenderer.render(sender.toMatrixItem(), inviteAvatarView) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 0740295191..9c2dc9b26d 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -163,7 +163,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? { val content = event.content?.toModel() ?: return null val roomId = event.roomId ?: return null - val dName = event.senderId?.let { session.getUser(it)?.displayName } + val dName = event.senderId?.let { session.getRoomMember(it, roomId)?.displayName } if (Membership.INVITE == content.membership) { val body = noticeEventFormatter.format(event, dName, session.getRoomSummary(roomId)) ?: stringProvider.getString(R.string.notification_new_invitation) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 9cfed991bb..d79d16a052 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -120,7 +120,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { null, false, System.currentTimeMillis(), - session.getUser(session.myUserId)?.displayName + session.getRoomMember(session.myUserId, room.roomId)?.displayName ?: context?.getString(R.string.notification_sender_me), session.myUserId, message, From f39b3365db4ca119cf2d5c5e9894538cf811fb86 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Sat, 7 Nov 2020 06:29:33 +0000 Subject: [PATCH 011/231] Translated using Weblate (Bulgarian) Currently translated at 100.0% (1933 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/bg/ --- vector/src/main/res/values-bg/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml index acd689387d..c62f245d88 100644 --- a/vector/src/main/res/values-bg/strings.xml +++ b/vector/src/main/res/values-bg/strings.xml @@ -2194,4 +2194,8 @@ Тема Тема на стаята (опция) Име на стая + Експортирай одит + Директно съобщение + Изпрати историята на заявките за споделяне на ключове + Няма повече резултати \ No newline at end of file From edc47b56a4ccd0fcca6870467fac3f53a0cf35a3 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Fri, 6 Nov 2020 20:30:52 +0000 Subject: [PATCH 012/231] Translated using Weblate (Catalan) Currently translated at 59.4% (1149 of 1933 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 | 109 ++++++++++++---------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 375133efc0..1fccac81d5 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -17,27 +17,27 @@ Missatges Sala Configuració - Detalls dels participants + Detalls del participant Historial - Informe d\'error + Informe d\'errors Detalls de la comunitat D\'acord Cancel·la Desa - Surt + Marxa Envia Reenvia - Suprimeix + Elimina Cita Comparteix Més tard Reenvia Enllaç permanent - Mostra el codi + Visualitza el codi font Visualitza el codi font desencriptat Elimina - Reanomena + Canvia el nom Informa del contingut Trucada activa Videoconferència en curs. @@ -46,17 +46,17 @@ Vídeo No es pot iniciar la trucada, prova-ho més tard Pot ser que algunes funcions no apareguin per falta de permisos… - Necessites permís per convidar a l\'inici d\'una videoconferència en aquesta sala + Necessites permisos d\'invitació per poder iniciar una conferència en aquesta sala No es pot iniciar la trucada Informació de la sessió - No s\'admeten videoconferències en sales xifrades + No s\'admeten conferències en sales xifrades Envia igualment o Convida Fora de línia Tanca la sessió - Trucada de veu + Trucada Videotrucada Cerca global Marca-ho tot com a llegit @@ -103,35 +103,35 @@ Convida Comunitats - No hi ha cap grup + No hi ha grups Envia els registres Envia els registres de fallada Envia una captura de pantalla Informa d\'un error - Descriviu l\'error. Què heu fet? Què esperàveu que passés? Què ha passat realment? - Descriviu el problema aquí - Els registres d\'aquest client s\'enviaran amb aquest informe d\'error per tal de diagnosticar problemes. Aquest informe d\'errors, així com també els registres i la captura de pantalla, no seran visibles de forma pública. Desmarqueu si preferiu enviar només el text: - Sembla que esteu prou frustrat per a estar sacsejant el telèfon. Voleu enviar un informe d\'error? - En l\'última execució l\'aplicació va fallar. Voleu enviar un informe d\'error? - L\'informe d\'error s\'ha enviat correctament - No s\'ha pogut enviar l\'informe d\'error (%s) + Descriu l\'error. Què has fet\? Què esperaves que passés\? Què ha passat realment\? + Descriu el problema aquí + Per tal de diagnosticar problemes, els registres d\'aquest client s\'enviaran juntament amb l\'informe d\'errors. Aquest informe d\'errors, així com també els registres i la captura de pantalla, no seran visibles públicament. Si prefereixes enviar només el text de dalt, desmarca: + Sembla que estàs sacsejant el telèfon amb frustració. Vols enviar un informe d\'errors\? + En l\'última execució l\'aplicació ha fallat. Vols obrir la pantalla d\'informe de fallada\? + L\'informe d\'errors s\'ha enviat correctament + No s\'ha pogut enviar l\'informe d\'errors (%s) En curs (%s%%) Envia a Llegit Uneix-te a la sala Nom d\'usuari Crear un compte - Entra - Desconnecta + Inicia sessió + Tanca la sessió URL del servidor URL del servidor d\'identitat Cerca - Inicia un xat nou - Inicia una trucada de veu - Inicia una vídeotrucada - Esteu segur que voleu començar una conversa amb %s? - Esteu segur que voleu començar una trucada de veu? - Esteu segur que voleu començar una trucada de vídeo? + Inicia un nou xat + Inicia una trucada + Inicia una videotrucada + Estàs segur que vols iniciar un nou xat amb %s\? + Estàs segur que vols iniciar una trucada\? + Estàs segur que vols iniciar una videotrucada\? Envia fitxers Fes una foto o un vídeo Fes una foto @@ -226,7 +226,7 @@ Trucant… Trucada d\'entrada Vídeotrucada d\'entrada - Trucada de veu d\'entrada + Trucada entrant Trucada en curs… No s\'està responent a la trucada. Ha fallat la connexió de mitjans @@ -300,7 +300,7 @@ No podràs desfer aquest canvi ja que estàs donant a l\'usuari el mateix nivell d\'autoritat que el teu. \nN\'estàs segur\? "Esteu segur que voleu convidar a %s a aquest xat?" - Esteu segur que voleu vetar aquest usuari en aquesta conversa? + Si vetes un usuari, se l\'expulsarà d\'aquesta sala i no podrà tornar a unir-s\'hi. Convida per ID CONTACTES LOCALS (%d) @@ -651,7 +651,7 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.Ignora la sol·licitut Avís! - Les trucades per a conferències estan en desenvolupament i poden no ser fiables. + Les conferències estan en desenvolupament i pot ser que no funcionin bé. Error de comandament Ordre no reconegut: %s @@ -691,7 +691,7 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.Només mencions Silencia Notificacions - Sacseja el dispositiu amb ràbia per a informar d\'un error + Sacseja el dispositiu amb ràbia per informar d\'un error Accions Llista de membres Sincronitzant… @@ -725,7 +725,7 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.%d missatges notificats no llegits - 1 canvi de membres + %d canvi de membres %d canvis de membres @@ -741,10 +741,10 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.Envia un adhesiu Envia un adhesiu Llicències de tercers - Descarrega + Baixa Parla - Neteja - Enviar veu + Esborra + Envia veu seguir amb… Ho sento, no s\'ha trobat cap aplicació externa per completar l\'acció. Tornar a demanar les claus d\'encriptació als teus altres dispositius. @@ -756,10 +756,10 @@ Atenció: es podria eliminar aquest fitxer si es desinstal·la l\'aplicació.No es pot dur a terme aquesta acció per falta de permisos. Error Alertes de sistema - Si és possible, escriviu si us plau la descripció en anglès. - Actualment no teniu cap conjunt d\'adhesius activat. - -En voleu afegir algun? + Si és possible, escriu la descripció en anglès. + Encara no tens cap paquet d\'adhesius activat. +\n +\nEn vols afegir algun\? %ds %ds @@ -862,7 +862,7 @@ En voleu afegir algun? Aquest servidor base ha assolit el seu límit màxim mensual d\'activitat d\'usuaris i alguns usuaris no podran identificar-s\'hi. Aquest servidor base ha assolit el seu límit mensual d\'activitat d\'usuaris. Si us plau %s per tal d\'incrementar aquest límit. - "Si us plau %s per continuar usant aquest servei." + Si us plau %s per continuar utilitzant aquest servei. Carrega en diferit els participants de la sala Millora el rendiment carregant només els participants de la sala a primera vista. El teu servidor encara no és compatible amb la càrrega en diferit dels participants d\'una sala. Prova-ho més tard. @@ -871,9 +871,9 @@ En voleu afegir algun? plega Accepta Trucada - Useu el to de Element per defecte per les trucades entrants + Utilitza el to de trucada d\'Element predeterminat per trucades entrants To de trucada entrant - Escolliu el to per les trucades: + Tria el to per les trucades: Expulsa Motiu Mostra la vista prèvia dels enllaços dins del xat en cas que el vostre servidor base suporti aquesta funcionalitat. @@ -885,10 +885,10 @@ En voleu afegir algun? Mostra els esdeveniments del compte Truca igualment Reviseu i accepteu les polítiques d\'aquest servidor base: - Trucada de vídeo en procés… + Videotrucada en procés… Executa les proves S\'està executant… (%1$d de %2$d) - El diagnòstic bàsic és correcte. Si encara no rebeu notificacions, envieu un informe d\'error per ajudar-nos a investigar. + El diagnòstic bàsic és correcte. Si encara no reps notificacions, envia un informe d\'errors per ajudar-nos a investigar-ho. Ha fallat una o més proves, proveu les solucions proposades. Paràmetres del sistema. Les notificacions són habilitades als paràmetres del sistema. @@ -941,7 +941,7 @@ A la pantalla següent se us demanarà que permeteu al Element executar-se sempr Importància de les notificacions per esdeveniment Diagnostica les notificacions Diagnòstic de la resolució de problemes - Ha fallat una o més proves, envieu un informe d\'error per ajudar-nos a investigar-ho. + Ha fallat una o més proves, envia un informe d\'errors per ajudar-nos a investigar-ho. Les notificacions no són permeses per a aquest dispositiu. Comproveu els paràmetres del Element. Paràmetres personalitzats. @@ -974,7 +974,7 @@ Aquest error és fora del control del Element i segons Google aquest error indic Comprova les restriccions del rerefons Ignora l\'optimització Configura les notificacions amb so - Configura les notificacions de les trucades + Configura les notificacions de trucada Configura les notificacions silencioses Seleccioneu el color de LED, la vibració, so… Gestió de claus criptogràfiques @@ -1018,8 +1018,8 @@ Avís: és possible que calgui suprimir el fitxer si es desinstal·la l\'aplicac Introduïu la clau de recuperació Recuperació de missatges Heu perdut la vostra clau de recuperació? Podeu establir una nova a les preferències. - "[%1$s] -Aquest error és fora del control del Element. Pot ocórrer per diferents raons. És possible que funcioni si ho torneu a provar més endavant. També podeu comprovar que el servei de Google Play no està restringit a l\'ús de dades a les preferències del sistema, o que el rellotge del dispositiu marca l\'hora correcta. També pot passar amb ROM personalitzades." + [%1$s] +\nAquest error està fora del control d\'Element. Pot ser causat diferents motius. És possible que torni a funcionar més endavant. També pots comprovar, a la configuració del sistema, que els Serveis de Google Play no tinguin cap restricció de dades o que l\'hora del dispositiu sigui la correcta. També pot passar amb ROMs personalitzades. [%1$s] Aquest error és fora del control del Element. No hi ha cap compte de Google al telèfon. Obrir el gestor de comptes i afegiu un compte de Google. Les restriccions de rerefons són inhabilitades per al Element. Aquesta prova s\'hauria d\'executar emprant dades mòbils (sense wifi). @@ -1066,7 +1066,7 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men La còpia de seguretat de les claus no ha finalitzat, espera… Si tanques sessió ara, perdràs els teus missatges xifrats S\'està fent la còpia de seguretat de les claus. Si tanques sessió ara, perdràs els teus missatges xifrats. - No vull els meus missatges encriptats + No vull els meus missatges xifrats Fent còpia de seguretat de les claus… Utilitza la còpia de seguretat de les claus N\'estàs segur\? @@ -1104,8 +1104,8 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Signatura He sigut jo Còpia de seguretat nova de la clau - Per evitar la pèrdua d\'accés als teus missatges encriptats, hauries d\'activar la còpia de seguretat segura a totes les teves sessions. - Perdràs l\'accés als teus missatges encriptats si no fas una còpia de seguretat de les teves claus abans de tancar la sessió. + Per evitar la pèrdua d\'accés als teus missatges xifrats, hauries d\'activar la còpia de seguretat segura a totes les teves sessions. + Perdràs l\'accés als teus missatges xifrats si no fas una còpia de seguretat de les teves claus abans de tancar la sessió. Es desarà una còpia encriptada de les vostres claus al vostre servidor base. Protegiu la vostra còpia de seguretat amb una contrasenya per tal de mantenir-la segura. \n \nPer màxima seguretat, aquesta contrasenya hauria de ser diferent de la contrasenya del vostre compte. @@ -1333,10 +1333,10 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Les reunions utilitzen les polítiques de seguretat i permisos de Jitsi. Tots els participants que es trobin dins sala veuran una invitació per unir-se mentre la teva reunió estigui en curs. Inicia una reunió d\'àudio Inicia una reunió de vídeo - Ja hi ha una videoconferència en curs! + Ja hi ha una conferència en curs! No tens permís per iniciar una trucada No tens permís per iniciar una trucada en aquesta sala - No tens permís per iniciar una videoconferència + No tens permís per iniciar una conferència No tens permís per iniciar una videoconferència en aquesta sala Reinicia Omet @@ -1345,4 +1345,11 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Revoca Cap Latn + Trucada activa (%s) + Demana confirmació abans d\'iniciar una trucada + Evita trucada accidental + La descripció és massa curta + Envia l\'historial de sol·licituds de compartició de claus + Revisa + Reprodueix \ No newline at end of file From ad64651532657b1b46d2cc499397e6fe325819bb Mon Sep 17 00:00:00 2001 From: random Date: Sat, 7 Nov 2020 10:45:01 +0000 Subject: [PATCH 013/231] Translated using Weblate (Italian) Currently translated at 100.0% (1933 of 1933 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index ae1fed2d19..d7eb7a29d4 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2250,4 +2250,8 @@ Argomento Argomento stanza (facoltativo) Nome stanza + Esporta revisione + Messaggio diretto + Invia cronologia di richieste condivisione chiave + Nessun altro risultato \ No newline at end of file From 62ca9a2a848e3b1411d595dfb5d196b3ec10a26d Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 14:37:02 +0000 Subject: [PATCH 014/231] Fix misleading identifier Signed-off-by: Dominic Fischer --- .../app/features/home/room/detail/RoomDetailViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 9efad1081f..81eedcef5f 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 @@ -164,7 +164,7 @@ class RoomDetailViewModel @AssistedInject constructor( getUnreadState() observeSyncState() observeEventDisplayedActions() - getDraftIfAny() + loadDraftIfAny() observeUnreadState() observeMyRoomMember() observeActiveRoomWidgets() @@ -495,7 +495,7 @@ class RoomDetailViewModel @AssistedInject constructor( } } - private fun getDraftIfAny() { + private fun loadDraftIfAny() { val currentDraft = room.getDraft() ?: return setState { copy( @@ -772,7 +772,7 @@ class RoomDetailViewModel @AssistedInject constructor( private fun popDraft() = withState { if (it.sendMode is SendMode.REGULAR && it.sendMode.fromSharing) { // If we were sharing, we want to get back our last value from draft - getDraftIfAny() + loadDraftIfAny() } else { // Otherwise we clear the composer and remove the draft from db setState { copy(sendMode = SendMode.REGULAR("", false)) } From dee6f358880554b9c3795f087947120d506a07fb Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 14:57:15 +0000 Subject: [PATCH 015/231] Fix draft bug Signed-off-by: Dominic Fischer --- .../app/features/home/room/detail/RoomDetailViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 81eedcef5f..102a0673d4 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 @@ -496,7 +496,7 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun loadDraftIfAny() { - val currentDraft = room.getDraft() ?: return + val currentDraft = room.getDraft() setState { copy( // Create a sendMode from a draft and retrieve the TimelineEvent @@ -517,6 +517,7 @@ class RoomDetailViewModel @AssistedInject constructor( SendMode.EDIT(timelineEvent, currentDraft.text) } } + else -> null } ?: SendMode.REGULAR("", fromSharing = false) ) } From c34750bdabdd607c17bfb95b25b6101eecbf626a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Nov 2020 16:49:31 +0100 Subject: [PATCH 016/231] Add Onuray in the core team authors --- AUTHORS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 4fb5b8c994..f6ff724fec 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -26,6 +26,11 @@ Even if we try to be able to work on all the functionalities, we have more knowl - Product manager, Android developer - Specialist on the crypto implementation. +## Onuray: Android developer + +[@onurays:matrix.org](https://matrix.to/#/@onurays:matrix.org) +- Android developer + # Other contributors First of all, we thank all contributors who use Element and report problems on this GitHub project or via the integrated rageshake function. From 16448c0bc5eac8ccfe5520fa33ff69962e200edd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Nov 2020 16:53:17 +0100 Subject: [PATCH 017/231] Add Github link to the authors --- AUTHORS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index f6ff724fec..823dbc7311 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -4,7 +4,7 @@ A full developer contributors list can be found [here](https://github.com/vector Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves. -## Benoit: Android team leader +## [Benoit](https://github.com/bmarty): Android team leader [@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org) - Android team leader and project leader, Android developer, GitHub community manager. @@ -12,7 +12,7 @@ Even if we try to be able to work on all the functionalities, we have more knowl - Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager. - Release manager on the Play Store -## François: Software architect +## [Ganfra](https://github.com/ganfra) (aka François): Software architect [@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org) - Software architect, Android developer @@ -20,13 +20,13 @@ Even if we try to be able to work on all the functionalities, we have more knowl - Work mainly on the global architecture of the project. - Specialist of the timeline, and lots of other features. -## Valere: Product manager, Android developer +## [Valere](https://github.com/BillCarsonFr): Product manager, Android developer [@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org) - Product manager, Android developer - Specialist on the crypto implementation. -## Onuray: Android developer +## [Onuray](https://github.com/onurays): Android developer [@onurays:matrix.org](https://matrix.to/#/@onurays:matrix.org) - Android developer From fd4b56572d9cba0e0915538fb4fb7ba578e9f7d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Nov 2020 10:36:50 +0100 Subject: [PATCH 018/231] Use also{} to log info --- .../crypto/store/db/RealmCryptoStore.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 72274aa70a..6b83c4f7d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1679,27 +1679,24 @@ internal class RealmCryptoStore @Inject constructor( // Only keep one week history realm.where() .lessThan(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, prevWeekTs) - .findAll().let { - Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") - it.deleteAllFromRealm() - } + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") } + .deleteAllFromRealm() // Clean the cancelled ones? realm.where() .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, OutgoingGossipingRequestState.CANCELLED.name) .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) - .findAll().let { - Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") - it.deleteAllFromRealm() - } + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") } + .deleteAllFromRealm() // Only keep one week history realm.where() .lessThan(GossipingEventEntityFields.AGE_LOCAL_TS, prevWeekTs) - .findAll().let { - Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") - it.deleteAllFromRealm() - } + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") } + .deleteAllFromRealm() // Can we do something for WithHeldSessionEntity? } From ea4e9b8e5e4d1183263710a17d5a2d812a4c43b4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Nov 2020 18:48:36 +0100 Subject: [PATCH 019/231] Fix "Riot is now Element" dialog displayed by mistake, after a logout --- .../im/vector/app/features/disclaimer/DisclaimerDialog.kt | 2 +- .../java/im/vector/app/features/settings/VectorPreferences.kt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt b/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt index c2cd2e11e3..028d37ff5f 100644 --- a/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt +++ b/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt @@ -28,7 +28,7 @@ import im.vector.app.features.settings.VectorSettingsUrls // Increase this value to show again the disclaimer dialog after an upgrade of the application private const val CURRENT_DISCLAIMER_VALUE = 2 -private const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE" +const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE" fun showDisclaimerDialog(activity: Activity) { val sharedPrefs = DefaultSharedPreferences.getInstance(activity) 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 295bb01265..5872c1fa1c 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 @@ -24,6 +24,7 @@ import com.squareup.seismic.ShakeDetector import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.features.disclaimer.SHARED_PREF_KEY import im.vector.app.features.homeserver.ServerUrlsRepository import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.extensions.tryOrNull @@ -248,6 +249,9 @@ class VectorPreferences @Inject constructor(private val context: Context) { // theme keysToKeep.add(ThemeUtils.APPLICATION_THEME_KEY) + // Disclaimer dialog + keysToKeep.add(SHARED_PREF_KEY) + // get all the existing keys val keys = defaultPrefs.all.keys From 07c805a019a14bbf201e6f5a4e553156176c7ec9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Nov 2020 23:22:12 +0100 Subject: [PATCH 020/231] Create sanity test with all screens path Introduce `com.schibsted.spain:barista` --- vector/build.gradle | 4 + .../java/im/vector/app/RegistrationTest.kt | 6 +- .../im/vector/app/SecurityBootstrapTest.kt | 2 +- .../im/vector/app/VerificationTestBase.kt | 116 +------ .../app/VerifySessionInteractiveTest.kt | 6 +- .../vector/app/VerifySessionPassphraseTest.kt | 6 +- .../vector/app/espresso/tools/WaitActivity.kt | 25 ++ .../vector/app/ui/UiAllScreensSanityTest.kt | 322 ++++++++++++++++++ .../java/im/vector/app/ui/UiTestBase.kt | 90 +++++ .../main/res/layout/fragment_home_detail.xml | 2 +- .../main/res/layout/fragment_room_list.xml | 3 +- 11 files changed, 462 insertions(+), 120 deletions(-) create mode 100644 vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt create mode 100644 vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt create mode 100644 vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt diff --git a/vector/build.gradle b/vector/build.gradle index ca7cb12e31..037b049a76 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -461,6 +461,10 @@ dependencies { androidTestImplementation "androidx.arch.core:core-testing:$arch_version" // Plant Timber tree for test androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' + // "The one who serves a great Espresso" + androidTestImplementation('com.schibsted.spain:barista:3.7.0') { + exclude group: 'org.jetbrains.kotlin' + } } if (getGradle().getStartParameter().getTaskRequests().toString().contains("Gplay")) { diff --git a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt index b88356db59..73ca94b148 100644 --- a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt +++ b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt @@ -67,7 +67,7 @@ class RegistrationTest { .perform(click()) // Enter local synapse - onView((withId(R.id.loginServerUrlFormHomeServerUrl))) + onView(withId(R.id.loginServerUrlFormHomeServerUrl)) .perform(typeText(homeServerUrl)) // Click on continue @@ -87,7 +87,7 @@ class RegistrationTest { .check(matches(isDisplayed())) // Ensure user id - onView((withId(R.id.loginField))) + onView(withId(R.id.loginField)) .perform(typeText(userId)) // Ensure login button not yet enabled @@ -95,7 +95,7 @@ class RegistrationTest { .check(matches(not(isEnabled()))) // Ensure password - onView((withId(R.id.passwordField))) + onView(withId(R.id.passwordField)) .perform(closeSoftKeyboard(), typeText(password)) // Submit diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index 3ab8fe7dd9..0d0ec3dd2b 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -79,7 +79,7 @@ class SecurityBootstrapTest : VerificationTestBase() { fun testBasicBootstrap() { val userId: String = existingSession!!.myUserId - doLogin(homeServerUrl, userId, password) + uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) // Thread.sleep(6000) withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt index 2a1b6d802f..a4b9983ff4 100644 --- a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt +++ b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt @@ -18,15 +18,11 @@ package im.vector.app import android.net.Uri import androidx.lifecycle.Observer -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.matcher.ViewMatchers +import im.vector.app.ui.UiTestBase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.hamcrest.CoreMatchers import org.junit.Assert import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixCallback @@ -43,108 +39,12 @@ abstract class VerificationTestBase { val password = "password" val homeServerUrl: String = "http://10.0.2.2:8080" - fun doLogin(homeServerUrl: String, userId: String, password: String) { - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_splash_submit))) + protected val uiTestBase = UiTestBase() - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .perform(ViewActions.click()) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerTitle)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_server_title))) - - // Chose custom server - Espresso.onView(ViewMatchers.withId(R.id.loginServerChoiceOther)) - .perform(ViewActions.click()) - - // Enter local synapse - Espresso.onView((ViewMatchers.withId(R.id.loginServerUrlFormHomeServerUrl))) - .perform(ViewActions.typeText(homeServerUrl)) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerUrlFormSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - - // Click on the signin button - Espresso.onView(ViewMatchers.withId(R.id.loginSignupSigninSignIn)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .perform(ViewActions.click()) - - // Ensure password flow supported - Espresso.onView(ViewMatchers.withId(R.id.loginField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - Espresso.onView(ViewMatchers.withId(R.id.passwordField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - - Espresso.onView((ViewMatchers.withId(R.id.loginField))) - .perform(ViewActions.typeText(userId)) - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isEnabled()))) - - Espresso.onView((ViewMatchers.withId(R.id.passwordField))) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.typeText(password)) - - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - } - - private fun createAccount(userId: String = "UiAutoTest", password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_splash_submit))) - - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .perform(ViewActions.click()) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerTitle)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_server_title))) - - // Chose custom server - Espresso.onView(ViewMatchers.withId(R.id.loginServerChoiceOther)) - .perform(ViewActions.click()) - - // Enter local synapse - Espresso.onView((ViewMatchers.withId(R.id.loginServerUrlFormHomeServerUrl))) - .perform(ViewActions.typeText(homeServerUrl)) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerUrlFormSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - - // Click on the signup button - Espresso.onView(ViewMatchers.withId(R.id.loginSignupSigninSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .perform(ViewActions.click()) - - // Ensure password flow supported - Espresso.onView(ViewMatchers.withId(R.id.loginField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - Espresso.onView(ViewMatchers.withId(R.id.passwordField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - - Espresso.onView((ViewMatchers.withId(R.id.loginField))) - .perform(ViewActions.typeText(userId)) - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isEnabled()))) - - Espresso.onView((ViewMatchers.withId(R.id.passwordField))) - .perform(ViewActions.typeText(password)) - - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - - Espresso.onView(ViewMatchers.withId(R.id.homeDrawerFragmentContainer)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - } - - fun createAccountAndSync(matrix: Matrix, userName: String, - password: String, - withInitialSync: Boolean): Session { + fun createAccountAndSync(matrix: Matrix, + userName: String, + password: String, + withInitialSync: Boolean): Session { val hs = createHomeServerConfig() doSync { @@ -174,7 +74,7 @@ abstract class VerificationTestBase { return session } - fun createHomeServerConfig(): HomeServerConnectionConfig { + private fun createHomeServerConfig(): HomeServerConnectionConfig { return HomeServerConnectionConfig.Builder() .withHomeServerUri(Uri.parse(homeServerUrl)) .build() @@ -200,7 +100,7 @@ abstract class VerificationTestBase { return result!! } - fun syncSession(session: Session) { + private fun syncSession(session: Session) { val lock = CountDownLatch(1) GlobalScope.launch(Dispatchers.Main) { session.open() } diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index d218b6ef7e..d9005e4a63 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -78,7 +78,7 @@ class VerifySessionInteractiveTest : VerificationTestBase() { fun checkVerifyPopup() { val userId: String = existingSession!!.myUserId - doLogin(homeServerUrl, userId, password) + uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) // Thread.sleep(6000) withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { @@ -215,10 +215,10 @@ class VerifySessionInteractiveTest : VerificationTestBase() { } fun signout() { - onView((withId(R.id.groupToolbarAvatarImageView))) + onView(withId(R.id.groupToolbarAvatarImageView)) .perform(click()) - onView((withId(R.id.homeDrawerHeaderSettingsView))) + onView(withId(R.id.homeDrawerHeaderSettingsView)) .perform(click()) onView(withText("General")) diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index f8c2a89ea8..8a21260ac7 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt @@ -88,7 +88,7 @@ class VerifySessionPassphraseTest : VerificationTestBase() { fun checkVerifyWithPassphrase() { val userId: String = existingSession!!.myUserId - doLogin(homeServerUrl, userId, password) + uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) // Thread.sleep(6000) withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { @@ -137,10 +137,10 @@ class VerifySessionPassphraseTest : VerificationTestBase() { onView(withId(R.id.ssss__root)).check(matches(isDisplayed())) } - onView((withId(R.id.ssss_passphrase_enter_edittext))) + onView(withId(R.id.ssss_passphrase_enter_edittext)) .perform(typeText(passphrase)) - onView((withId(R.id.ssss_passphrase_submit))) + onView(withId(R.id.ssss_passphrase_submit)) .perform(click()) System.out.println("*** passphrase 1") diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt new file mode 100644 index 0000000000..2cdca62c74 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.espresso.tools + +import android.app.Activity +import im.vector.app.activityIdlingResource +import im.vector.app.withIdlingResource + +inline fun waitUntilActivityVisible(noinline block: (() -> Unit)) { + withIdlingResource(activityIdlingResource(T::class.java), block) +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt new file mode 100644 index 0000000000..e237a103e4 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -0,0 +1,322 @@ +/* + * 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.ui + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.longClick +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.schibsted.spain.barista.assertion.BaristaListAssertions.assertListItemCount +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickBack +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogNegativeButton +import com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogPositiveButton +import com.schibsted.spain.barista.interaction.BaristaEditTextInteractions.writeTo +import com.schibsted.spain.barista.interaction.BaristaListInteractions.clickListItem +import com.schibsted.spain.barista.interaction.BaristaListInteractions.clickListItemChild +import com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions.clickMenu +import com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions.openMenu +import im.vector.app.EspressoHelper +import im.vector.app.R +import im.vector.app.SleepViewAction +import im.vector.app.activityIdlingResource +import im.vector.app.espresso.tools.waitUntilActivityVisible +import im.vector.app.features.MainActivity +import im.vector.app.features.createdirect.CreateDirectRoomActivity +import im.vector.app.features.home.HomeActivity +import im.vector.app.features.home.room.detail.RoomDetailActivity +import im.vector.app.features.login.LoginActivity +import im.vector.app.features.roomdirectory.RoomDirectoryActivity +import im.vector.app.initialSyncIdlingResource +import im.vector.app.withIdlingResource +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.UUID + +/** + * This test aim to open every possible screen of the application + */ +@RunWith(AndroidJUnit4::class) +@LargeTest +class UiAllScreensSanityTest { + + @get:Rule + val activityRule = ActivityScenarioRule(MainActivity::class.java) + + private val uiTestBase = UiTestBase() + + @Test + fun allScreensTest() { + // Create an account + val userId = "UiTest_" + UUID.randomUUID().toString() + uiTestBase.createAccount(userId = userId) + + withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { + assertDisplayed(R.id.roomListContainer) + closeSoftKeyboard() + } + + val activity = EspressoHelper.getCurrentActivity()!! + val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession() + + withIdlingResource(initialSyncIdlingResource(uiSession)) { + assertDisplayed(R.id.roomListContainer) + } + + assertDisplayed(R.id.bottomNavigationView) + + // Settings + navigateToSettings() + + // Create DM + clickOn(R.id.bottom_action_people) + createDm() + + // Create Room + // First navigate to the other tab + clickOn(R.id.bottom_action_rooms) + createRoom() + + assertDisplayed(R.id.bottomNavigationView) + + // Long click on the room + onView(withId(R.id.roomListView)) + .perform( + actionOnItem( + hasDescendant(withText(R.string.room_displayname_empty_room)), + longClick() + ) + ) + pressBack() + + uiTestBase.signout() + + // We have sent a message in a e2e room, accept to loose it + clickOn(R.id.exitAnywayButton) + // Dark pattern + clickDialogNegativeButton() + + // Login again on the same account + waitUntilActivityVisible { + assertDisplayed(R.id.loginSplashLogo) + } + + uiTestBase.login(userId) + ignoreVerification() + + uiTestBase.signout() + clickDialogPositiveButton() + } + + private fun ignoreVerification() { + Thread.sleep(6000) + val activity = EspressoHelper.getCurrentActivity()!! + + val popup = activity.findViewById(com.tapadoo.alerter.R.id.llAlertBackground) + activity.runOnUiThread { + popup.performClick() + } + + assertDisplayed(R.id.bottomSheetFragmentContainer) + + onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000)) + + clickOn(R.string.skip) + assertDisplayed(R.string.are_you_sure) + clickOn(R.string.skip) + } + + private fun createRoom() { + clickOn(R.id.createGroupRoomButton) + waitUntilActivityVisible { + assertDisplayed(R.id.publicRoomsList) + } + clickOn(R.string.create_new_room) + + // Create + assertListItemCount(R.id.createRoomForm, 10) + clickListItemChild(R.id.createRoomForm, 9, R.id.form_submit_button) + + waitUntilActivityVisible { + assertDisplayed(R.id.roomDetailContainer) + } + + clickOn(R.id.attachmentButton) + clickBack() + + // Send a message + writeTo(R.id.composerEditText, "Hello world!") + clickOn(R.id.sendButton) + + navigateToRoomSettings() + + // Long click on the message + onView(withId(R.id.recyclerView)) + .perform( + actionOnItem( + hasDescendant(withText("Hello world!")), + longClick() + ) + ) + pressBack() + + // Menu + openMenu() + pressBack() + clickMenu(R.id.voice_call) + pressBack() + clickMenu(R.id.video_call) + pressBack() + + pressBack() + } + + private fun navigateToRoomSettings() { + clickOn(R.id.roomToolbarTitleView) + assertDisplayed(R.id.roomProfileAvatarView) + + // Room settings + clickListItem(R.id.matrixProfileRecyclerView, 3) + pressBack() + + // Notifications + clickListItem(R.id.matrixProfileRecyclerView, 5) + pressBack() + + assertDisplayed(R.id.roomProfileAvatarView) + + // People + clickListItem(R.id.matrixProfileRecyclerView, 7) + assertDisplayed(R.id.inviteUsersButton) + navigateToRoomPeople() + // Fab + navigateToInvite() + pressBack() + pressBack() + + assertDisplayed(R.id.roomProfileAvatarView) + + // Uploads + clickListItem(R.id.matrixProfileRecyclerView, 9) + // File tab + clickOn(R.string.uploads_files_title) + pressBack() + + assertDisplayed(R.id.roomProfileAvatarView) + + // Leave + clickListItem(R.id.matrixProfileRecyclerView, 13) + clickDialogNegativeButton() + + // Menu share + // clickMenu(R.id.roomProfileShareAction) + // pressBack() + + pressBack() + } + + private fun navigateToInvite() { + assertDisplayed(R.id.inviteUsersButton) + clickOn(R.id.inviteUsersButton) + closeSoftKeyboard() + pressBack() + } + + private fun navigateToRoomPeople() { + // Open first user + clickListItem(R.id.recyclerView, 1) + assertDisplayed(R.id.memberProfilePowerLevelView) + + // Verification + clickListItem(R.id.matrixProfileRecyclerView, 1) + clickBack() + + // Role + clickListItem(R.id.matrixProfileRecyclerView, 3) + clickDialogNegativeButton() + + clickBack() + } + + private fun createDm() { + clickOn(R.id.createChatRoomButton) + + withIdlingResource(activityIdlingResource(CreateDirectRoomActivity::class.java)) { + assertDisplayed(R.id.addByMatrixId) + } + + closeSoftKeyboard() + pressBack() + pressBack() + } + + private fun navigateToSettings() { + clickOn(R.id.groupToolbarAvatarImageView) + clickOn(R.id.homeDrawerHeaderSettingsView) + + clickOn(R.string.settings_general_title) + // TODO + pressBack() + + clickOn(R.string.settings_notifications) + // TODO + pressBack() + + clickOn(R.string.settings_preferences) + // TODO + pressBack() + + clickOn(R.string.preference_voice_and_video) + // TODO + pressBack() + + clickOn(R.string.settings_ignored_users) + // TODO + pressBack() + + clickOn(R.string.settings_security_and_privacy) + // TODO + pressBack() + + clickOn(R.string.room_settings_labs_pref_title) + // TODO + pressBack() + + clickOn(R.string.settings_advanced_settings) + // TODO + pressBack() + + clickOn(R.string.preference_root_help_about) + // TODO + pressBack() + + pressBack() + } +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt b/vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt new file mode 100644 index 0000000000..ed174b50a2 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt @@ -0,0 +1,90 @@ +/* + * 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.ui + +import androidx.test.espresso.Espresso.closeSoftKeyboard +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import com.schibsted.spain.barista.assertion.BaristaEnabledAssertions.assertDisabled +import com.schibsted.spain.barista.assertion.BaristaEnabledAssertions.assertEnabled +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.interaction.BaristaEditTextInteractions.writeTo +import im.vector.app.R +import im.vector.app.espresso.tools.waitUntilActivityVisible +import im.vector.app.features.home.HomeActivity +import im.vector.app.waitForView + +class UiTestBase { + fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { + initSession(true, userId, password, homeServerUrl) + } + + fun login(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { + initSession(false, userId, password, homeServerUrl) + } + + private fun initSession(createAccount: Boolean, + userId: String, + password: String, + homeServerUrl: String) { + assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit) + clickOn(R.id.loginSplashSubmit) + assertDisplayed(R.id.loginServerTitle, R.string.login_server_title) + // Chose custom server + clickOn(R.id.loginServerChoiceOther) + // Enter local synapse + writeTo(R.id.loginServerUrlFormHomeServerUrl, homeServerUrl) + assertEnabled(R.id.loginServerUrlFormSubmit) + closeSoftKeyboard() + clickOn(R.id.loginServerUrlFormSubmit) + onView(isRoot()).perform(waitForView(withId(R.id.loginSignupSigninSubmit))) + + if (createAccount) { + // Click on the signup button + assertDisplayed(R.id.loginSignupSigninSubmit) + clickOn(R.id.loginSignupSigninSubmit) + } else { + // Click on the signin button + assertDisplayed(R.id.loginSignupSigninSignIn) + clickOn(R.id.loginSignupSigninSignIn) + } + + // Ensure password flow supported + assertDisplayed(R.id.loginField) + assertDisplayed(R.id.passwordField) + + writeTo(R.id.loginField, userId) + assertDisabled(R.id.loginSubmit) + writeTo(R.id.passwordField, password) + assertEnabled(R.id.loginSubmit) + + closeSoftKeyboard() + clickOn(R.id.loginSubmit) + + // Wait + waitUntilActivityVisible { + assertDisplayed(R.id.homeDetailFragmentContainer) + } + } + + fun signout() { + clickOn(R.id.groupToolbarAvatarImageView) + clickOn(R.id.homeDrawerHeaderSignoutView) + } +} diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index 83bb9994f8..d9a2470343 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -105,7 +105,7 @@ + android:overScrollMode="always" + tools:listitem="@layout/item_room" /> Date: Wed, 4 Nov 2020 10:36:15 +0100 Subject: [PATCH 021/231] Make id for recyclerView less generic, and remove unused layout --- .../vector/app/ui/UiAllScreensSanityTest.kt | 2 +- .../debug/sas/DebugSasEmojiActivity.kt | 4 +- .../discovery/DiscoverySettingsFragment.kt | 4 +- .../home/room/detail/RoomDetailFragment.kt | 22 +-- .../reactions/EmojiSearchResultFragment.kt | 4 +- .../banned/RoomBannedMemberListFragment.kt | 4 +- .../members/RoomMemberListFragment.kt | 6 +- .../settings/RoomSettingsFragment.kt | 5 +- .../CrossSigningSettingsFragment.kt | 4 +- .../devices/VectorSettingsDevicesFragment.kt | 4 +- .../settings/devtools/AccountDataFragment.kt | 4 +- .../GossipingEventsPaperTrailFragment.kt | 4 +- .../IncomingKeyRequestListFragment.kt | 4 +- .../OutgoingKeyRequestListFragment.kt | 4 +- .../VectorSettingsIgnoredUsersFragment.kt | 4 +- .../settings/push/PushGatewaysFragment.kt | 4 +- .../settings/push/PushRulesFragment.kt | 4 +- .../threepids/ThreePidsSettingsFragment.kt | 4 +- .../signout/soft/SoftLogoutFragment.kt | 6 +- .../userdirectory/KnownUsersFragment.kt | 4 +- .../userdirectory/UserDirectoryFragment.kt | 5 +- .../layout/fragment_create_direct_room.xml | 143 ------------------ ...ent_create_direct_room_directory_users.xml | 2 +- .../res/layout/fragment_generic_recycler.xml | 2 +- .../main/res/layout/fragment_known_users.xml | 2 +- .../main/res/layout/fragment_room_detail.xml | 6 +- .../layout/fragment_room_setting_generic.xml | 2 +- .../res/layout/fragment_user_directory.xml | 2 +- 28 files changed, 61 insertions(+), 204 deletions(-) delete mode 100644 vector/src/main/res/layout/fragment_create_direct_room.xml diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index e237a103e4..6136a20557 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -178,7 +178,7 @@ class UiAllScreensSanityTest { navigateToRoomSettings() // Long click on the message - onView(withId(R.id.recyclerView)) + onView(withId(R.id.timelineRecyclerView)) .perform( actionOnItem( hasDescendant(withText("Hello world!")), diff --git a/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt index f22784bc12..869058eff6 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt @@ -30,12 +30,12 @@ class DebugSasEmojiActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.fragment_generic_recycler) val controller = SasEmojiController() - recyclerView.configureWith(controller) + genericRecyclerView.configureWith(controller) controller.setData(SasState(getAllVerificationEmojis())) } override fun onDestroy() { - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroy() } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt index bfbc00b15a..c43f223d1f 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt @@ -55,7 +55,7 @@ class DiscoverySettingsFragment @Inject constructor( sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java) controller.listener = this - recyclerView.configureWith(controller) + genericRecyclerView.configureWith(controller) sharedViewModel.navigateEvent.observeEvent(this) { when (it) { @@ -74,7 +74,7 @@ class DiscoverySettingsFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() controller.listener = null super.onDestroyView() } 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 9c6c473a7f..2cf21066d6 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 @@ -508,7 +508,7 @@ class RoomDetailFragment @Inject constructor( modelBuildListener = null autoCompleter.clear() debouncer.cancelAll() - recyclerView.cleanup() + timelineRecyclerView.cleanup() super.onDestroyView() } @@ -535,7 +535,7 @@ class RoomDetailFragment @Inject constructor( jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager( jumpToBottomView, debouncer, - recyclerView, + timelineRecyclerView, layoutManager ) } @@ -558,7 +558,7 @@ class RoomDetailFragment @Inject constructor( if (scrollPosition == null) { scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId) } else { - recyclerView.stopScroll() + timelineRecyclerView.stopScroll() layoutManager.scrollToPosition(scrollPosition) } } @@ -969,14 +969,14 @@ class RoomDetailFragment @Inject constructor( timelineEventController.callback = this timelineEventController.timeline = roomDetailViewModel.timeline - recyclerView.trackItemsVisibilityChange() + timelineRecyclerView.trackItemsVisibilityChange() layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) - scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(recyclerView, layoutManager, timelineEventController) - recyclerView.layoutManager = layoutManager - recyclerView.itemAnimator = null - recyclerView.setHasFixedSize(true) + scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(timelineRecyclerView, layoutManager, timelineEventController) + timelineRecyclerView.layoutManager = layoutManager + timelineRecyclerView.itemAnimator = null + timelineRecyclerView.setHasFixedSize(true) modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) it.dispatchTo(scrollOnNewMessageCallback) @@ -985,7 +985,7 @@ class RoomDetailFragment @Inject constructor( jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() } timelineEventController.addModelBuildListener(modelBuildListener) - recyclerView.adapter = timelineEventController.adapter + timelineRecyclerView.adapter = timelineEventController.adapter if (vectorPreferences.swipeToReplyIsEnabled()) { val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler { @@ -1015,9 +1015,9 @@ class RoomDetailFragment @Inject constructor( } val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), R.drawable.ic_reply, quickReplyHandler) val touchHelper = ItemTouchHelper(swipeCallback) - touchHelper.attachToRecyclerView(recyclerView) + touchHelper.attachToRecyclerView(timelineRecyclerView) } - recyclerView.addGlidePreloader( + timelineRecyclerView.addGlidePreloader( epoxyController = timelineEventController, requestManager = GlideApp.with(this), preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ -> diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt index 685f0dd64e..28df628cf1 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt @@ -41,12 +41,12 @@ class EmojiSearchResultFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) sharedViewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java) epoxyController.listener = this - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { epoxyController.listener = null - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt index 81b977ac97..797e6c8aa3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt @@ -56,7 +56,7 @@ class RoomBannedMemberListFragment @Inject constructor( roomMemberListController.callback = this setupToolbar(roomSettingsToolbar) setupSearchView() - recyclerView.configureWith(roomMemberListController, hasFixedSize = true) + roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) viewModel.observeViewEvents { when (it) { @@ -83,7 +83,7 @@ class RoomBannedMemberListFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + roomSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index 1b3e33a161..fb42b8ce27 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -57,7 +57,7 @@ class RoomMemberListFragment @Inject constructor( setupToolbar(roomSettingsToolbar) setupSearchView() setupInviteUsersButton() - recyclerView.configureWith(roomMemberListController, hasFixedSize = true) + roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) } private fun setupInviteUsersButton() { @@ -65,7 +65,7 @@ class RoomMemberListFragment @Inject constructor( navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId) } // Hide FAB when list is scrolling - recyclerView.addOnScrollListener( + roomSettingsRecyclerView.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { when (newState) { @@ -99,7 +99,7 @@ class RoomMemberListFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + roomSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 57521f7d80..81c8ba4c77 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -72,7 +72,7 @@ class RoomSettingsFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) controller.callback = this setupToolbar(roomSettingsToolbar) - recyclerView.configureWith(controller, hasFixedSize = true) + roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true @@ -93,7 +93,8 @@ class RoomSettingsFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + controller.callback = null + roomSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt index ebeac5aca1..f21ec2e8f4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -68,12 +68,12 @@ class CrossSigningSettingsFragment @Inject constructor( } private fun setupRecyclerView() { - recyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + genericRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) controller.interactionListener = this } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() controller.interactionListener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index ae45989a81..a317536d5d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -61,7 +61,7 @@ class VectorSettingsDevicesFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true devicesController.callback = this - recyclerView.configureWith(devicesController, showDivider = true) + genericRecyclerView.configureWith(devicesController, showDivider = true) viewModel.observeViewEvents { when (it) { is DevicesViewEvents.Loading -> showLoading(it.message) @@ -97,7 +97,7 @@ class VectorSettingsDevicesFragment @Inject constructor( override fun onDestroyView() { devicesController.callback = null - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt index 07508f41a2..40b910c1ab 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt @@ -57,13 +57,13 @@ class AccountDataFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() epoxyController.interactionListener = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index 0ceb8e148d..af8881ba92 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -50,13 +50,13 @@ class GossipingEventsPaperTrailFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() epoxyController.interactionListener = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt index 35f46d9c74..6e205ceceb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt @@ -45,11 +45,11 @@ class IncomingKeyRequestListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt index a82b5dd6c9..20132d8047 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -41,13 +41,13 @@ class OutgoingKeyRequestListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) // epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() // epoxyController.interactionListener = null } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 2588eef59b..5ad7258cec 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -49,7 +49,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true ignoredUsersController.callback = this - recyclerView.configureWith(ignoredUsersController) + genericRecyclerView.configureWith(ignoredUsersController) viewModel.observeViewEvents { when (it) { is IgnoredUsersViewEvents.Loading -> showLoading(it.message) @@ -60,7 +60,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun onDestroyView() { ignoredUsersController.callback = null - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt index e6e9ce3753..0075d8ef5a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt @@ -59,11 +59,11 @@ class PushGatewaysFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt index c361e21254..c5ad04380b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt @@ -43,11 +43,11 @@ class PushRulesFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index 81033281d8..12ff51dcbd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -54,7 +54,7 @@ class ThreePidsSettingsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController) + genericRecyclerView.configureWith(epoxyController) epoxyController.interactionListener = this viewModel.observeViewEvents { @@ -73,7 +73,7 @@ class ThreePidsSettingsFragment @Inject constructor( override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() epoxyController.interactionListener = null } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 64b71356ec..dbd5028401 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -70,12 +70,12 @@ class SoftLogoutFragment @Inject constructor( } private fun setupRecyclerView() { - recyclerView.configureWith(softLogoutController) + genericRecyclerView.configureWith(softLogoutController) softLogoutController.listener = this } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() softLogoutController.listener = null super.onDestroyView() } @@ -121,7 +121,7 @@ class SoftLogoutFragment @Inject constructor( } private fun cleanupUi() { - recyclerView.hideKeyboard() + genericRecyclerView.hideKeyboard() } override fun forgetPasswordClicked() { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt index 0ca46cd154..ec684e8eea 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt @@ -82,7 +82,7 @@ class KnownUsersFragment @Inject constructor( override fun onDestroyView() { knownUsersController.callback = null - recyclerView.cleanup() + knownUsersRecyclerView.cleanup() super.onDestroyView() } @@ -124,7 +124,7 @@ class KnownUsersFragment @Inject constructor( private fun setupRecyclerView() { knownUsersController.callback = this // Don't activate animation as we might have way to much item animation when filtering - recyclerView.configureWith(knownUsersController, disableItemAnimation = true) + knownUsersRecyclerView.configureWith(knownUsersController, disableItemAnimation = true) } private fun setupFilterView() { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt index 8787946bf4..70ea9141e7 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt @@ -28,7 +28,6 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.setupAsSearch import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.recyclerView import kotlinx.android.synthetic.main.fragment_user_directory.* import org.matrix.android.sdk.api.session.user.model.User import javax.inject.Inject @@ -51,14 +50,14 @@ class UserDirectoryFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + userDirectoryRecyclerView.cleanup() directRoomController.callback = null super.onDestroyView() } private fun setupRecyclerView() { directRoomController.callback = this - recyclerView.configureWith(directRoomController) + userDirectoryRecyclerView.configureWith(directRoomController) } private fun setupSearchByMatrixIdView() { diff --git a/vector/src/main/res/layout/fragment_create_direct_room.xml b/vector/src/main/res/layout/fragment_create_direct_room.xml deleted file mode 100644 index 8d2bc68fa8..0000000000 --- a/vector/src/main/res/layout/fragment_create_direct_room.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml b/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml index d1bc9e4645..616c28c31e 100644 --- a/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml +++ b/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml @@ -90,7 +90,7 @@ app:layout_constraintTop_toBottomOf="@+id/createDirectRoomSearchByIdContainer" /> Date: Wed, 4 Nov 2020 11:53:24 +0100 Subject: [PATCH 022/231] Go deeper in the settings --- .../vector/app/ui/UiAllScreensSanityTest.kt | 121 ++++++++++++++++-- 1 file changed, 110 insertions(+), 11 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index 6136a20557..da68bba3f6 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -22,7 +22,6 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.action.ViewActions.closeSoftKeyboard import androidx.test.espresso.action.ViewActions.longClick -import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasDescendant @@ -54,6 +53,7 @@ import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.login.LoginActivity import im.vector.app.features.roomdirectory.RoomDirectoryActivity import im.vector.app.initialSyncIdlingResource +import im.vector.app.waitForView import im.vector.app.withIdlingResource import org.junit.Rule import org.junit.Test @@ -251,7 +251,7 @@ class UiAllScreensSanityTest { private fun navigateToRoomPeople() { // Open first user - clickListItem(R.id.recyclerView, 1) + clickListItem(R.id.roomSettingsRecyclerView, 1) assertDisplayed(R.id.memberProfilePowerLevelView) // Verification @@ -282,41 +282,140 @@ class UiAllScreensSanityTest { clickOn(R.id.homeDrawerHeaderSettingsView) clickOn(R.string.settings_general_title) - // TODO + navigateToSettingsGeneral() pressBack() clickOn(R.string.settings_notifications) - // TODO + navigateToSettingsNotifications() pressBack() clickOn(R.string.settings_preferences) - // TODO + navigateToSettingsPreferences() pressBack() clickOn(R.string.preference_voice_and_video) - // TODO pressBack() clickOn(R.string.settings_ignored_users) - // TODO pressBack() clickOn(R.string.settings_security_and_privacy) - // TODO + navigateToSettingsSecurity() pressBack() clickOn(R.string.room_settings_labs_pref_title) - // TODO pressBack() clickOn(R.string.settings_advanced_settings) - // TODO + navigateToSettingsAdvanced() pressBack() clickOn(R.string.preference_root_help_about) - // TODO + navigateToSettingsHelp() pressBack() pressBack() } + + private fun navigateToSettingsHelp() { + /* + clickOn(R.string.settings_app_info_link_title) + Cannot go back... + pressBack() + clickOn(R.string.settings_copyright) + pressBack() + clickOn(R.string.settings_app_term_conditions) + pressBack() + clickOn(R.string.settings_privacy_policy) + pressBack() + clickOn(R.string.settings_third_party_notices) + pressBack() + */ + } + + private fun navigateToSettingsAdvanced() { + /* + TODO Find a way to scroll + clickOn(R.string.settings_notifications_targets) + pressBack() + clickOn(R.string.settings_push_rules) + pressBack() + + // Enable developer mode + clickOn(R.string.settings_developer_mode) + + clickOn(R.string.settings_account_data) + clickOn("m.push_rules") + pressBack() + pressBack() + clickOn(R.string.settings_key_requests) + pressBack() + + // Disable developer mode + clickOn(R.string.settings_developer_mode) + */ + } + + private fun navigateToSettingsSecurity() { + clickOn(R.string.settings_active_sessions_show_all) + pressBack() + /* + TODO Find a way to scroll + clickOn(R.string.encryption_message_recovery) + // TODO go deeper here + pressBack() + clickOn(R.string.encryption_export_e2e_room_keys) + pressBack() + */ + } + + private fun navigateToSettingsPreferences() { + clickOn(R.string.settings_interface_language) + onView(ViewMatchers.isRoot()) + .perform(waitForView(withText("Dansk (Danmark)"))) + pressBack() + clickOn(R.string.settings_theme) + clickDialogNegativeButton() + clickOn(R.string.font_size) + clickDialogNegativeButton() + } + + private fun navigateToSettingsNotifications() { + clickOn(R.string.settings_notification_advanced) + pressBack() + /* + clickOn(R.string.settings_noisy_notifications_preferences) + TODO Cannot go back + pressBack() + clickOn(R.string.settings_silent_notifications_preferences) + pressBack() + clickOn(R.string.settings_call_notifications_preferences) + pressBack() + */ + clickOn(R.string.settings_notification_troubleshoot) + pressBack() + } + + private fun navigateToSettingsGeneral() { + clickOn(R.string.settings_profile_picture) + clickDialogPositiveButton() + clickOn(R.string.settings_display_name) + clickDialogNegativeButton() + clickOn(R.string.settings_password) + clickDialogNegativeButton() + clickOn(R.string.settings_emails_and_phone_numbers_title) + pressBack() + clickOn(R.string.settings_discovery_manage) + clickOn(R.string.add_identity_server) + pressBack() + pressBack() + /* TODO Find a way to scroll + // Identity server + clickListItem(android.preference.R.id.recycler_view, 30) + pressBack() + // Deactivate account + clickListItem(R.id.recycler_view, 32) + pressBack() + */ + } } From be3bc175bf62ff1edcf95ed256e4f483ea302b69 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Mon, 9 Nov 2020 18:23:45 +0000 Subject: [PATCH 023/231] Update CHANGES.md Signed-off-by: Dominic Fischer --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index fa30acabd5..87ea4c031d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: Bugfix 🐛: - Fix issue when updating the avatar of a room + - Fix issue when restoring draft after sharing (#2287) Translations 🗣: - From a37af307f41af399e661b33bec5827e1ae6efbda Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Nov 2020 18:28:23 +0100 Subject: [PATCH 024/231] Making progress --- CHANGES.md | 6 ++ .../app/espresso/tools/EspressoPreference.kt | 46 +++++++++ .../vector/app/ui/UiAllScreensSanityTest.kt | 99 +++++++++++++------ .../app/features/popup/PopupAlertManager.kt | 2 +- 4 files changed, 122 insertions(+), 31 deletions(-) create mode 100644 vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt diff --git a/CHANGES.md b/CHANGES.md index fa30acabd5..5a37061259 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,9 @@ SDK API changes ⚠️: Build 🧱: - +Test: + - Add `allScreensTest` to cover all screens of the app + Other changes: - Upgrade Realm dependency to 10.0.0 @@ -1032,5 +1035,8 @@ SDK API changes ⚠️: Build 🧱: - +Test: + - + Other changes: - diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt new file mode 100644 index 0000000000..bf60ad681f --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.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.espresso.tools + +import android.widget.Switch +import androidx.annotation.StringRes +import androidx.preference.Preference +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onData +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem +import androidx.test.espresso.matcher.PreferenceMatchers.withKey +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.withClassName +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import im.vector.app.R +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.instanceOf + +fun clickOnPreference(@StringRes textResId: Int) { + onView(withId(R.id.recycler_view)) + .perform(actionOnItem( + hasDescendant(withText(textResId)), click())) +} + +fun clickOnSwitchPreference(preferenceKey: String) { + onData(allOf(`is`(instanceOf(Preference::class.java)), withKey(preferenceKey))) + .onChildView(withClassName(`is`(Switch::class.java.name))).perform(click()) +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index da68bba3f6..1c05a5d529 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -25,6 +25,7 @@ import androidx.test.espresso.action.ViewActions.longClick import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.rules.ActivityScenarioRule @@ -34,6 +35,7 @@ import com.schibsted.spain.barista.assertion.BaristaListAssertions.assertListIte import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickBack import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.longClickOn import com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogNegativeButton import com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogPositiveButton import com.schibsted.spain.barista.interaction.BaristaEditTextInteractions.writeTo @@ -45,6 +47,7 @@ import im.vector.app.EspressoHelper import im.vector.app.R import im.vector.app.SleepViewAction import im.vector.app.activityIdlingResource +import im.vector.app.espresso.tools.clickOnPreference import im.vector.app.espresso.tools.waitUntilActivityVisible import im.vector.app.features.MainActivity import im.vector.app.features.createdirect.CreateDirectRoomActivity @@ -58,6 +61,7 @@ import im.vector.app.withIdlingResource import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import java.lang.Thread.sleep import java.util.UUID /** @@ -72,6 +76,7 @@ class UiAllScreensSanityTest { private val uiTestBase = UiTestBase() + // Last passing: 2020-11-09 @Test fun allScreensTest() { // Create an account @@ -133,6 +138,8 @@ class UiAllScreensSanityTest { uiTestBase.signout() clickDialogPositiveButton() + + // TODO Deactivate account instead of logout? } private fun ignoreVerification() { @@ -178,14 +185,7 @@ class UiAllScreensSanityTest { navigateToRoomSettings() // Long click on the message - onView(withId(R.id.timelineRecyclerView)) - .perform( - actionOnItem( - hasDescendant(withText("Hello world!")), - longClick() - ) - ) - pressBack() + longClickOnMessageTest() // Menu openMenu() @@ -198,6 +198,47 @@ class UiAllScreensSanityTest { pressBack() } + private fun longClickOnMessageTest() { + // Test quick reaction + longClickOnMessage() + // Add quick reaction + clickOn("👍") + + sleep(1000) + + // Open reactions + longClickOn("👍") + pressBack() + + // Test add reaction + longClickOnMessage() + clickOn(R.string.message_add_reaction) + // Filter + // TODO clickMenu(R.id.search) + clickListItem(R.id.emojiRecyclerView, 4) + + // Test Edit mode + longClickOnMessage() + clickOn(R.string.edit) + // TODO Cancel action + writeTo(R.id.composerEditText, "Hello universe!") + clickOn(R.id.sendButton) + // Open edit history + longClickOnMessage("Hello universe! (edited)") + clickOn(R.string.message_view_edit_history) + pressBack() + } + + private fun longClickOnMessage(text: String = "Hello world!") { + onView(withId(R.id.timelineRecyclerView)) + .perform( + actionOnItem( + hasDescendant(withText(text)), + longClick() + ) + ) + } + private fun navigateToRoomSettings() { clickOn(R.id.roomToolbarTitleView) assertDisplayed(R.id.roomProfileAvatarView) @@ -327,51 +368,51 @@ class UiAllScreensSanityTest { clickOn(R.string.settings_app_term_conditions) pressBack() clickOn(R.string.settings_privacy_policy) - pressBack() - clickOn(R.string.settings_third_party_notices) pressBack() */ + clickOn(R.string.settings_third_party_notices) + clickDialogPositiveButton() } private fun navigateToSettingsAdvanced() { - /* - TODO Find a way to scroll - clickOn(R.string.settings_notifications_targets) - pressBack() - clickOn(R.string.settings_push_rules) + clickOnPreference(R.string.settings_notifications_targets) pressBack() + clickOnPreference(R.string.settings_push_rules) + pressBack() + + /* TODO P2 test developer screens // Enable developer mode - clickOn(R.string.settings_developer_mode) + clickOnSwitchPreference("SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY") - clickOn(R.string.settings_account_data) + clickOnPreference(R.string.settings_account_data) clickOn("m.push_rules") pressBack() pressBack() - clickOn(R.string.settings_key_requests) + clickOnPreference(R.string.settings_key_requests) pressBack() // Disable developer mode - clickOn(R.string.settings_developer_mode) + clickOnSwitchPreference("SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY") */ } private fun navigateToSettingsSecurity() { - clickOn(R.string.settings_active_sessions_show_all) + clickOnPreference(R.string.settings_active_sessions_show_all) pressBack() - /* - TODO Find a way to scroll - clickOn(R.string.encryption_message_recovery) + + clickOnPreference(R.string.encryption_message_recovery) // TODO go deeper here pressBack() - clickOn(R.string.encryption_export_e2e_room_keys) + /* Cannot exit + clickOnPreference(R.string.encryption_export_e2e_room_keys) pressBack() */ } private fun navigateToSettingsPreferences() { clickOn(R.string.settings_interface_language) - onView(ViewMatchers.isRoot()) + onView(isRoot()) .perform(waitForView(withText("Dansk (Danmark)"))) pressBack() clickOn(R.string.settings_theme) @@ -392,7 +433,7 @@ class UiAllScreensSanityTest { clickOn(R.string.settings_call_notifications_preferences) pressBack() */ - clickOn(R.string.settings_notification_troubleshoot) + clickOnPreference(R.string.settings_notification_troubleshoot) pressBack() } @@ -409,13 +450,11 @@ class UiAllScreensSanityTest { clickOn(R.string.add_identity_server) pressBack() pressBack() - /* TODO Find a way to scroll // Identity server - clickListItem(android.preference.R.id.recycler_view, 30) + clickOnPreference(R.string.settings_identity_server) pressBack() // Deactivate account - clickListItem(R.id.recycler_view, 32) + clickOnPreference(R.string.settings_deactivate_my_account) pressBack() - */ } } diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index 665eb93428..b2257b250a 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -46,7 +46,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy? = null private var currentAlerter: VectorAlert? = null - private val alertFiFo = ArrayList() + private val alertFiFo = mutableListOf() fun postVectorAlert(alert: VectorAlert) { synchronized(alertFiFo) { From 8f78c4a0fb3741d6a52eaf5d5d4225797f745452 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 9 Nov 2020 21:08:53 +0100 Subject: [PATCH 025/231] Fix issue of verification banner displayed after logout --- .../features/home/HomeActivityViewEvents.kt | 2 +- .../features/home/HomeActivityViewModel.kt | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index 2a29e13572..7753a7f58b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -22,6 +22,6 @@ import org.matrix.android.sdk.api.util.MatrixItem sealed class HomeActivityViewEvents : VectorViewEvents { data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents() - data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() + data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents() object PromptToEnableSessionPush : HomeActivityViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 48a71db35c..6d0bb7395b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -78,29 +78,30 @@ class HomeActivityViewModel @AssistedInject constructor( } private fun observeCrossSigningReset() { - val safeActiveSession = activeSessionHolder.getSafeActiveSession() - val crossSigningService = safeActiveSession - ?.cryptoService() - ?.crossSigningService() - onceTrusted = crossSigningService - ?.allPrivateKeysKnown() ?: false + val safeActiveSession = activeSessionHolder.getSafeActiveSession() ?: return + + onceTrusted = safeActiveSession + .cryptoService() + .crossSigningService().allPrivateKeysKnown() safeActiveSession - ?.rx() - ?.liveCrossSigningInfo(safeActiveSession.myUserId) - ?.subscribe { + .rx() + .liveCrossSigningInfo(safeActiveSession.myUserId) + .subscribe { val isVerified = it.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { // cross signing keys have been reset // Tigger a popup to re-verify - _viewEvents.post( - HomeActivityViewEvents.OnCrossSignedInvalidated( - safeActiveSession.getUser(safeActiveSession.myUserId)?.toMatrixItem() - ) - ) + // Note: user can be null in case of logout + safeActiveSession.getUser(safeActiveSession.myUserId) + ?.toMatrixItem() + ?.let { user -> + _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user)) + } } onceTrusted = isVerified - }?.disposeOnClear() + } + .disposeOnClear() } private fun observeInitialSync() { From bcd384c31c5cb6250ed13c0ff281a2c741203629 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 14:46:19 +0100 Subject: [PATCH 026/231] Small cleanup --- .../vector/app/features/media/BaseAttachmentProvider.kt | 5 ++++- .../app/features/media/DataAttachmentRoomProvider.kt | 1 - .../app/features/media/RoomEventsAttachmentProvider.kt | 9 ++++----- .../app/features/media/VectorAttachmentViewerActivity.kt | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index 3846e56ecf..f99ed2e324 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -30,7 +30,10 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.file.FileService import java.io.File -abstract class BaseAttachmentProvider(val imageContentRenderer: ImageContentRenderer, val fileService: FileService) : AttachmentSourceProvider { +abstract class BaseAttachmentProvider( + private val imageContentRenderer: ImageContentRenderer, + protected val fileService: FileService +) : AttachmentSourceProvider { interface InteractionListener { fun onDismissTapped() diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 085153a721..70305f2fd8 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -31,7 +31,6 @@ import java.io.File class DataAttachmentRoomProvider( private val attachments: List, private val room: Room?, - private val initialIndex: Int, imageContentRenderer: ImageContentRenderer, private val dateFormatter: VectorDateFormatter, fileService: FileService) : BaseAttachmentProvider(imageContentRenderer, fileService) { diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 5c0c33d078..574751ec36 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -40,7 +40,6 @@ import javax.inject.Inject class RoomEventsAttachmentProvider( private val attachments: List, - private val initialIndex: Int, imageContentRenderer: ImageContentRenderer, private val dateFormatter: VectorDateFormatter, fileService: FileService @@ -167,11 +166,11 @@ class AttachmentProviderFactory @Inject constructor( private val session: Session ) { - fun createProvider(attachments: List, initialIndex: Int): RoomEventsAttachmentProvider { - return RoomEventsAttachmentProvider(attachments, initialIndex, imageContentRenderer, vectorDateFormatter, session.fileService()) + fun createProvider(attachments: List): RoomEventsAttachmentProvider { + return RoomEventsAttachmentProvider(attachments, imageContentRenderer, vectorDateFormatter, session.fileService()) } - fun createProvider(attachments: List, room: Room?, initialIndex: Int): DataAttachmentRoomProvider { - return DataAttachmentRoomProvider(attachments, room, initialIndex, imageContentRenderer, vectorDateFormatter, session.fileService()) + fun createProvider(attachments: List, room: Room?): DataAttachmentRoomProvider { + return DataAttachmentRoomProvider(attachments, room, imageContentRenderer, vectorDateFormatter, session.fileService()) } } diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 9302be502d..92b0e0c14c 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -118,7 +118,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen val inMemoryData = intent.getParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA) if (inMemoryData != null) { - val sourceProvider = dataSourceFactory.createProvider(inMemoryData, room, initialIndex) + val sourceProvider = dataSourceFactory.createProvider(inMemoryData, room) val index = inMemoryData.indexOfFirst { it.eventId == args.eventId } initialIndex = index sourceProvider.interactionListener = this @@ -137,7 +137,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen val index = events.indexOfFirst { it.eventId == args.eventId } initialIndex = index - val sourceProvider = dataSourceFactory.createProvider(events, index) + val sourceProvider = dataSourceFactory.createProvider(events) sourceProvider.interactionListener = this setSourceProvider(sourceProvider) this.currentSourceProvider = sourceProvider From 345e8a067987a89629510918a44cc4fda5abbd0e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:06:51 +0100 Subject: [PATCH 027/231] i18n --- .../features/media/BaseAttachmentProvider.kt | 4 ++- .../media/DataAttachmentRoomProvider.kt | 11 +++++-- .../media/RoomEventsAttachmentProvider.kt | 30 +++++++++++++++---- vector/src/main/res/values/strings.xml | 2 ++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index f99ed2e324..f8ffe99b77 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -22,6 +22,7 @@ import android.view.View import android.widget.ImageView import com.bumptech.glide.request.target.CustomViewTarget import com.bumptech.glide.request.transition.Transition +import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.ImageLoaderTarget @@ -32,7 +33,8 @@ import java.io.File abstract class BaseAttachmentProvider( private val imageContentRenderer: ImageContentRenderer, - protected val fileService: FileService + protected val fileService: FileService, + protected val stringProvider: StringProvider ) : AttachmentSourceProvider { interface InteractionListener { diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 70305f2fd8..289e9912dc 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -19,8 +19,10 @@ package im.vector.app.features.media import android.content.Context import android.view.View import androidx.core.view.isVisible +import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.isVideoMessage @@ -33,7 +35,9 @@ class DataAttachmentRoomProvider( private val room: Room?, imageContentRenderer: ImageContentRenderer, private val dateFormatter: VectorDateFormatter, - fileService: FileService) : BaseAttachmentProvider(imageContentRenderer, fileService) { + fileService: FileService, + stringProvider: StringProvider +) : BaseAttachmentProvider(imageContentRenderer, fileService, stringProvider) { override fun getItemCount(): Int = attachments.size @@ -78,7 +82,10 @@ class DataAttachmentRoomProvider( val timeLineEvent = room?.getTimeLineEvent(item.eventId) if (timeLineEvent != null) { val dateString = dateFormatter.format(timeLineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - overlayView?.updateWith("${position + 1} of ${attachments.size}", "${timeLineEvent.senderInfo.displayName} $dateString") + overlayView?.updateWith( + counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, attachments.size), + senderInfo = "${timeLineEvent.senderInfo.displayName} $dateString" + ) overlayView?.videoControlsGroup?.isVisible = timeLineEvent.root.isVideoMessage() } else { overlayView?.updateWith("", "") diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 574751ec36..caaaabb4b3 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -19,8 +19,10 @@ package im.vector.app.features.media import android.content.Context import android.view.View import androidx.core.view.isVisible +import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session @@ -42,8 +44,9 @@ class RoomEventsAttachmentProvider( private val attachments: List, imageContentRenderer: ImageContentRenderer, private val dateFormatter: VectorDateFormatter, - fileService: FileService -) : BaseAttachmentProvider(imageContentRenderer, fileService) { + fileService: FileService, + stringProvider: StringProvider +) : BaseAttachmentProvider(imageContentRenderer, fileService, stringProvider) { override fun getItemCount(): Int { return attachments.size @@ -128,7 +131,10 @@ class RoomEventsAttachmentProvider( super.overlayViewAtPosition(context, position) val item = attachments[position] val dateString = dateFormatter.format(item.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - overlayView?.updateWith("${position + 1} of ${attachments.size}", "${item.senderInfo.displayName} $dateString") + overlayView?.updateWith( + counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, attachments.size), + senderInfo = "${item.senderInfo.displayName} $dateString" + ) overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage() return overlayView } @@ -163,14 +169,28 @@ class RoomEventsAttachmentProvider( class AttachmentProviderFactory @Inject constructor( private val imageContentRenderer: ImageContentRenderer, private val vectorDateFormatter: VectorDateFormatter, + private val stringProvider: StringProvider, private val session: Session ) { fun createProvider(attachments: List): RoomEventsAttachmentProvider { - return RoomEventsAttachmentProvider(attachments, imageContentRenderer, vectorDateFormatter, session.fileService()) + return RoomEventsAttachmentProvider( + attachments, + imageContentRenderer, + vectorDateFormatter, + session.fileService(), + stringProvider + ) } fun createProvider(attachments: List, room: Room?): DataAttachmentRoomProvider { - return DataAttachmentRoomProvider(attachments, room, imageContentRenderer, vectorDateFormatter, session.fileService()) + return DataAttachmentRoomProvider( + attachments, + room, + imageContentRenderer, + vectorDateFormatter, + session.fileService(), + stringProvider + ) } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 45d9d40ba6..8ac2b6410d 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1852,6 +1852,8 @@ Rotate and crop Couldn\'t handle share data + %1$d of %2$d + MEDIA There are no media in this room FILES From 816301bf8dad7b9acb163cb27d5e56225625be66 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:09:49 +0100 Subject: [PATCH 028/231] More cleanup --- .../media/VectorAttachmentViewerActivity.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 92b0e0c14c..30af1fdd09 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -119,33 +119,29 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen val inMemoryData = intent.getParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA) if (inMemoryData != null) { val sourceProvider = dataSourceFactory.createProvider(inMemoryData, room) - val index = inMemoryData.indexOfFirst { it.eventId == args.eventId } - initialIndex = index + initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId } sourceProvider.interactionListener = this setSourceProvider(sourceProvider) this.currentSourceProvider = sourceProvider if (savedInstanceState == null) { - pager2.setCurrentItem(index, false) + pager2.setCurrentItem(initialIndex, false) // The page change listener is not notified of the change... pager2.post { - onSelectedPositionChanged(index) + onSelectedPositionChanged(initialIndex) } } } else { - val events = room?.getAttachmentMessages() - ?: emptyList() - val index = events.indexOfFirst { it.eventId == args.eventId } - initialIndex = index - + val events = room?.getAttachmentMessages().orEmpty() + initialIndex = events.indexOfFirst { it.eventId == args.eventId } val sourceProvider = dataSourceFactory.createProvider(events) sourceProvider.interactionListener = this setSourceProvider(sourceProvider) this.currentSourceProvider = sourceProvider if (savedInstanceState == null) { - pager2.setCurrentItem(index, false) + pager2.setCurrentItem(initialIndex, false) // The page change listener is not notified of the change... pager2.post { - onSelectedPositionChanged(index) + onSelectedPositionChanged(initialIndex) } } } From 83467b8597adf75e8ff05925d5af2dce67b8ff68 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:12:21 +0100 Subject: [PATCH 029/231] Fix crash on AttachmentViewer (#2365) Not sure how to reproduce it, but if the value is -1, it will crash for sure --- CHANGES.md | 1 + .../app/features/media/VectorAttachmentViewerActivity.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 26418c75f1..44f5eee57d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Improvements 🙌: - Open an existing DM instead of creating a new one (#2319) Bugfix 🐛: + - Fix crash on AttachmentViewer (#2365) - Fix issue when updating the avatar of a room (new avatar vanishing) - Discard change dialog displayed by mistake when avatar has been updated diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 30af1fdd09..778df64ed5 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -119,7 +119,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen val inMemoryData = intent.getParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA) if (inMemoryData != null) { val sourceProvider = dataSourceFactory.createProvider(inMemoryData, room) - initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId } + initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) sourceProvider.interactionListener = this setSourceProvider(sourceProvider) this.currentSourceProvider = sourceProvider @@ -132,7 +132,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen } } else { val events = room?.getAttachmentMessages().orEmpty() - initialIndex = events.indexOfFirst { it.eventId == args.eventId } + initialIndex = events.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) val sourceProvider = dataSourceFactory.createProvider(events) sourceProvider.interactionListener = this setSourceProvider(sourceProvider) From ca70ddb810c7dd19435259160539a2aa013c5328 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:15:42 +0100 Subject: [PATCH 030/231] DRY --- .../media/VectorAttachmentViewerActivity.kt | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 778df64ed5..4105a1c55f 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -70,7 +70,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen private var initialIndex = 0 private var isAnimatingOut = false - var currentSourceProvider: BaseAttachmentProvider? = null + private var currentSourceProvider: BaseAttachmentProvider? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -117,32 +117,22 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen val room = args.roomId?.let { session.getRoom(it) } val inMemoryData = intent.getParcelableArrayListExtra(EXTRA_IN_MEMORY_DATA) - if (inMemoryData != null) { - val sourceProvider = dataSourceFactory.createProvider(inMemoryData, room) + val sourceProvider = if (inMemoryData != null) { initialIndex = inMemoryData.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) - sourceProvider.interactionListener = this - setSourceProvider(sourceProvider) - this.currentSourceProvider = sourceProvider - if (savedInstanceState == null) { - pager2.setCurrentItem(initialIndex, false) - // The page change listener is not notified of the change... - pager2.post { - onSelectedPositionChanged(initialIndex) - } - } + dataSourceFactory.createProvider(inMemoryData, room) } else { val events = room?.getAttachmentMessages().orEmpty() initialIndex = events.indexOfFirst { it.eventId == args.eventId }.coerceAtLeast(0) - val sourceProvider = dataSourceFactory.createProvider(events) - sourceProvider.interactionListener = this - setSourceProvider(sourceProvider) - this.currentSourceProvider = sourceProvider - if (savedInstanceState == null) { - pager2.setCurrentItem(initialIndex, false) - // The page change listener is not notified of the change... - pager2.post { - onSelectedPositionChanged(initialIndex) - } + dataSourceFactory.createProvider(events) + } + sourceProvider.interactionListener = this + setSourceProvider(sourceProvider) + currentSourceProvider = sourceProvider + if (savedInstanceState == null) { + pager2.setCurrentItem(initialIndex, false) + // The page change listener is not notified of the change... + pager2.post { + onSelectedPositionChanged(initialIndex) } } @@ -274,7 +264,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen } override fun onShareTapped() { - this.currentSourceProvider?.getFileForSharing(currentPosition) { data -> + currentSourceProvider?.getFileForSharing(currentPosition) { data -> if (data != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) { shareMedia(this@VectorAttachmentViewerActivity, data, getMimeTypeFromUri(this@VectorAttachmentViewerActivity, data.toUri())) } From a2a2015af6ee190b578a4a6a7092a73a52f0e24f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Tue, 10 Nov 2020 17:17:05 +0300 Subject: [PATCH 031/231] Exclude yourself when decorating rooms which are direct or don't have more than 2 users. --- CHANGES.md | 1 + .../sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fa30acabd5..d61c312fca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: Bugfix 🐛: - Fix issue when updating the avatar of a room + - Exclude yourself when decorating rooms which are direct or don't have more than 2 users (#2370) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index f28fe7d642..665d770e7f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -241,9 +241,9 @@ internal class UpdateTrustWorker(context: Context, private fun computeRoomShield(activeMemberUserIds: List, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") // The set of “all users” depends on the type of room: - // For regular / topic rooms, all users including yourself, are considered when decorating a room + // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room - val listToCheck = if (roomSummaryEntity.isDirect) { + val listToCheck = if (roomSummaryEntity.isDirect || activeMemberUserIds.size <= 2) { activeMemberUserIds.filter { it != myUserId } } else { activeMemberUserIds From c9defec75dd921ac7ba717849eaa3eb37aeb2b37 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Nov 2020 15:18:41 +0100 Subject: [PATCH 032/231] Update CHANGES and clean files --- CHANGES.md | 1 + .../vector/app/features/home/room/detail/RoomDetailViewState.kt | 1 - .../main/java/im/vector/app/features/invite/VectorInviteView.kt | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 26418c75f1..21a5416ee3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Open an existing DM instead of creating a new one (#2319) + - Use RoomMember instead of User in the context of a Room. Bugfix 🐛: - Fix issue when updating the avatar of a room (new avatar vanishing) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index d7d084f6b7..38b93f9363 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent 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 /** diff --git a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt index f238261057..5e8c5b3cca 100644 --- a/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt +++ b/vector/src/main/java/im/vector/app/features/invite/VectorInviteView.kt @@ -28,7 +28,6 @@ import im.vector.app.features.home.AvatarRenderer import kotlinx.android.synthetic.main.vector_invite_view.view.* import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject From 510f8ae0f54df7c49baa08917bb645cbd5d4c841 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:23:22 +0100 Subject: [PATCH 033/231] DRY --- .../features/media/BaseAttachmentProvider.kt | 26 ++++++++++++++++-- .../media/DataAttachmentRoomProvider.kt | 27 ++++--------------- .../media/RoomEventsAttachmentProvider.kt | 22 +++------------ 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index f8ffe99b77..d77f8ad0e2 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -20,21 +20,28 @@ import android.content.Context import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView +import androidx.core.view.isVisible import com.bumptech.glide.request.target.CustomViewTarget import com.bumptech.glide.request.transition.Transition +import im.vector.app.R +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.ImageLoaderTarget import im.vector.lib.attachmentviewer.VideoLoaderTarget import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.file.FileService +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import java.io.File abstract class BaseAttachmentProvider( private val imageContentRenderer: ImageContentRenderer, protected val fileService: FileService, - protected val stringProvider: StringProvider + private val dateFormatter: VectorDateFormatter, + private val stringProvider: StringProvider ) : AttachmentSourceProvider { interface InteractionListener { @@ -48,7 +55,7 @@ abstract class BaseAttachmentProvider( protected var overlayView: AttachmentOverlayView? = null - override fun overlayViewAtPosition(context: Context, position: Int): View? { + final override fun overlayViewAtPosition(context: Context, position: Int): View? { if (position == -1) return null if (overlayView == null) { overlayView = AttachmentOverlayView(context) @@ -65,9 +72,24 @@ abstract class BaseAttachmentProvider( interactionListener?.videoSeekTo(percent) } } + + val timelineEvent = getTimelineEventAtPosition(position) + if (timelineEvent != null) { + val dateString = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) + overlayView?.updateWith( + counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, getItemCount()), + senderInfo = "${timelineEvent.senderInfo.displayName} $dateString" + ) + overlayView?.videoControlsGroup?.isVisible = timelineEvent.root.isVideoMessage() + } else { + overlayView?.updateWith("", "") + } + return overlayView } + abstract fun getTimelineEventAtPosition(position: Int): TimelineEvent? + override fun loadImage(target: ImageLoaderTarget, info: AttachmentInfo.Image) { (info.data as? ImageContentRenderer.Data)?.let { imageContentRenderer.render(it, target.contextView(), object : CustomViewTarget(target.contextView()) { diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 289e9912dc..57587b8db2 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -16,28 +16,23 @@ package im.vector.app.features.media -import android.content.Context -import android.view.View -import androidx.core.view.isVisible -import im.vector.app.R -import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import java.io.File class DataAttachmentRoomProvider( private val attachments: List, private val room: Room?, imageContentRenderer: ImageContentRenderer, - private val dateFormatter: VectorDateFormatter, + dateFormatter: VectorDateFormatter, fileService: FileService, stringProvider: StringProvider -) : BaseAttachmentProvider(imageContentRenderer, fileService, stringProvider) { +) : BaseAttachmentProvider(imageContentRenderer, fileService, dateFormatter, stringProvider) { override fun getItemCount(): Int = attachments.size @@ -76,21 +71,9 @@ class DataAttachmentRoomProvider( } } - override fun overlayViewAtPosition(context: Context, position: Int): View? { - super.overlayViewAtPosition(context, position) + override fun getTimelineEventAtPosition(position: Int): TimelineEvent? { val item = attachments[position] - val timeLineEvent = room?.getTimeLineEvent(item.eventId) - if (timeLineEvent != null) { - val dateString = dateFormatter.format(timeLineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - overlayView?.updateWith( - counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, attachments.size), - senderInfo = "${timeLineEvent.senderInfo.displayName} $dateString" - ) - overlayView?.videoControlsGroup?.isVisible = timeLineEvent.root.isVideoMessage() - } else { - overlayView?.updateWith("", "") - } - return overlayView + return room?.getTimeLineEvent(item.eventId) } override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index caaaabb4b3..13224c5626 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -16,17 +16,11 @@ package im.vector.app.features.media -import android.content.Context -import android.view.View -import androidx.core.view.isVisible -import im.vector.app.R -import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.Room @@ -43,10 +37,10 @@ import javax.inject.Inject class RoomEventsAttachmentProvider( private val attachments: List, imageContentRenderer: ImageContentRenderer, - private val dateFormatter: VectorDateFormatter, + dateFormatter: VectorDateFormatter, fileService: FileService, stringProvider: StringProvider -) : BaseAttachmentProvider(imageContentRenderer, fileService, stringProvider) { +) : BaseAttachmentProvider(imageContentRenderer, fileService, dateFormatter, stringProvider) { override fun getItemCount(): Int { return attachments.size @@ -127,16 +121,8 @@ class RoomEventsAttachmentProvider( } } - override fun overlayViewAtPosition(context: Context, position: Int): View? { - super.overlayViewAtPosition(context, position) - val item = attachments[position] - val dateString = dateFormatter.format(item.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME) - overlayView?.updateWith( - counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, attachments.size), - senderInfo = "${item.senderInfo.displayName} $dateString" - ) - overlayView?.videoControlsGroup?.isVisible = item.root.isVideoMessage() - return overlayView + override fun getTimelineEventAtPosition(position: Int): TimelineEvent? { + return attachments[position] } override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { From bfcd4b82504b6c4c1fc1d10095553242ad2a4bbd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:26:59 +0100 Subject: [PATCH 034/231] Extract AttachmentProviderFactory to its own file --- .../media/AttachmentProviderFactory.kt | 53 +++++++++++++++++++ .../media/RoomEventsAttachmentProvider.kt | 31 ----------- 2 files changed, 53 insertions(+), 31 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/media/AttachmentProviderFactory.kt diff --git a/vector/src/main/java/im/vector/app/features/media/AttachmentProviderFactory.kt b/vector/src/main/java/im/vector/app/features/media/AttachmentProviderFactory.kt new file mode 100644 index 0000000000..b549e01551 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/media/AttachmentProviderFactory.kt @@ -0,0 +1,53 @@ +/* + * 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.media + +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class AttachmentProviderFactory @Inject constructor( + private val imageContentRenderer: ImageContentRenderer, + private val vectorDateFormatter: VectorDateFormatter, + private val stringProvider: StringProvider, + private val session: Session +) { + + fun createProvider(attachments: List): RoomEventsAttachmentProvider { + return RoomEventsAttachmentProvider( + attachments, + imageContentRenderer, + vectorDateFormatter, + session.fileService(), + stringProvider + ) + } + + fun createProvider(attachments: List, room: Room?): DataAttachmentRoomProvider { + return DataAttachmentRoomProvider( + attachments, + room, + imageContentRenderer, + vectorDateFormatter, + session.fileService(), + stringProvider + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 13224c5626..4ffd416011 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -20,10 +20,8 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.file.FileService -import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent @@ -32,7 +30,6 @@ import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File -import javax.inject.Inject class RoomEventsAttachmentProvider( private val attachments: List, @@ -152,31 +149,3 @@ class RoomEventsAttachmentProvider( } } -class AttachmentProviderFactory @Inject constructor( - private val imageContentRenderer: ImageContentRenderer, - private val vectorDateFormatter: VectorDateFormatter, - private val stringProvider: StringProvider, - private val session: Session -) { - - fun createProvider(attachments: List): RoomEventsAttachmentProvider { - return RoomEventsAttachmentProvider( - attachments, - imageContentRenderer, - vectorDateFormatter, - session.fileService(), - stringProvider - ) - } - - fun createProvider(attachments: List, room: Room?): DataAttachmentRoomProvider { - return DataAttachmentRoomProvider( - attachments, - room, - imageContentRenderer, - vectorDateFormatter, - session.fileService(), - stringProvider - ) - } -} From 45e534bbf50a1884a0a0cee1f8f7a15934967181 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:33:24 +0100 Subject: [PATCH 035/231] DRY again --- .../vector/app/features/media/BaseAttachmentProvider.kt | 7 +++++-- .../app/features/media/DataAttachmentRoomProvider.kt | 6 ++---- .../app/features/media/RoomEventsAttachmentProvider.kt | 8 ++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index d77f8ad0e2..208d5e28f8 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -37,7 +37,8 @@ import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import java.io.File -abstract class BaseAttachmentProvider( +abstract class BaseAttachmentProvider( + protected val attachments: List, private val imageContentRenderer: ImageContentRenderer, protected val fileService: FileService, private val dateFormatter: VectorDateFormatter, @@ -53,7 +54,9 @@ abstract class BaseAttachmentProvider( var interactionListener: InteractionListener? = null - protected var overlayView: AttachmentOverlayView? = null + private var overlayView: AttachmentOverlayView? = null + + final override fun getItemCount() = attachments.size final override fun overlayViewAtPosition(context: Context, position: Int): View? { if (position == -1) return null diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 57587b8db2..02b9c034e9 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -26,15 +26,13 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import java.io.File class DataAttachmentRoomProvider( - private val attachments: List, + attachments: List, private val room: Room?, imageContentRenderer: ImageContentRenderer, dateFormatter: VectorDateFormatter, fileService: FileService, stringProvider: StringProvider -) : BaseAttachmentProvider(imageContentRenderer, fileService, dateFormatter, stringProvider) { - - override fun getItemCount(): Int = attachments.size +) : BaseAttachmentProvider(attachments, imageContentRenderer, fileService, dateFormatter, stringProvider) { override fun getAttachmentInfoAt(position: Int): AttachmentInfo { return attachments[position].let { diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 4ffd416011..da6cc5d570 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -32,16 +32,12 @@ import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File class RoomEventsAttachmentProvider( - private val attachments: List, + attachments: List, imageContentRenderer: ImageContentRenderer, dateFormatter: VectorDateFormatter, fileService: FileService, stringProvider: StringProvider -) : BaseAttachmentProvider(imageContentRenderer, fileService, dateFormatter, stringProvider) { - - override fun getItemCount(): Int { - return attachments.size - } +) : BaseAttachmentProvider(attachments, imageContentRenderer, fileService, dateFormatter, stringProvider) { override fun getAttachmentInfoAt(position: Int): AttachmentInfo { return attachments[position].let { From f57fc827feeaa37753221f0d1167f9dd954f8ff2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 15:36:22 +0100 Subject: [PATCH 036/231] Last cleanup --- .../im/vector/app/features/media/BaseAttachmentProvider.kt | 4 +++- .../app/features/media/DataAttachmentRoomProvider.kt | 6 +++--- .../app/features/media/RoomEventsAttachmentProvider.kt | 7 +++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index 208d5e28f8..e23b905919 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -38,7 +38,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import java.io.File abstract class BaseAttachmentProvider( - protected val attachments: List, + private val attachments: List, private val imageContentRenderer: ImageContentRenderer, protected val fileService: FileService, private val dateFormatter: VectorDateFormatter, @@ -58,6 +58,8 @@ abstract class BaseAttachmentProvider( final override fun getItemCount() = attachments.size + protected fun getItem(position: Int) = attachments[position] + final override fun overlayViewAtPosition(context: Context, position: Int): View? { if (position == -1) return null if (overlayView == null) { diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 02b9c034e9..18312b4aa0 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -35,7 +35,7 @@ class DataAttachmentRoomProvider( ) : BaseAttachmentProvider(attachments, imageContentRenderer, fileService, dateFormatter, stringProvider) { override fun getAttachmentInfoAt(position: Int): AttachmentInfo { - return attachments[position].let { + return getItem(position).let { when (it) { is ImageContentRenderer.Data -> { if (it.mimeType == "image/gif") { @@ -70,12 +70,12 @@ class DataAttachmentRoomProvider( } override fun getTimelineEventAtPosition(position: Int): TimelineEvent? { - val item = attachments[position] + val item = getItem(position) return room?.getTimeLineEvent(item.eventId) } override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { - val item = attachments[position] + val item = getItem(position) fileService.downloadFile( downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE, id = item.eventId, diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index da6cc5d570..1e2761dde0 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -40,7 +40,7 @@ class RoomEventsAttachmentProvider( ) : BaseAttachmentProvider(attachments, imageContentRenderer, fileService, dateFormatter, stringProvider) { override fun getAttachmentInfoAt(position: Int): AttachmentInfo { - return attachments[position].let { + return getItem(position).let { val content = it.root.getClearContent().toModel() as? MessageWithAttachmentContent if (content is MessageImageContent) { val data = ImageContentRenderer.Data( @@ -115,11 +115,11 @@ class RoomEventsAttachmentProvider( } override fun getTimelineEventAtPosition(position: Int): TimelineEvent? { - return attachments[position] + return getItem(position) } override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { - attachments[position].let { timelineEvent -> + getItem(position).let { timelineEvent -> val messageContent = timelineEvent.root.getClearContent().toModel() as? MessageWithAttachmentContent @@ -144,4 +144,3 @@ class RoomEventsAttachmentProvider( } } } - From d1848fd5f7d69ef24fcb9f0e20e2c899d7a5813d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 10 Nov 2020 16:01:19 +0100 Subject: [PATCH 037/231] Fix compilation issue :) --- .../vector/app/features/media/VectorAttachmentViewerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 4105a1c55f..e7f4806e31 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -70,7 +70,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen private var initialIndex = 0 private var isAnimatingOut = false - private var currentSourceProvider: BaseAttachmentProvider? = null + private var currentSourceProvider: BaseAttachmentProvider<*>? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) From 3ce8deec07512cfeb813cbf624f617d82c1467b2 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 12:24:47 +0000 Subject: [PATCH 038/231] Convert RoomCryptoService to suspend functions Signed-off-by: Dominic Fischer --- .../android/sdk/common/CryptoTestHelper.kt | 4 ++-- .../api/session/room/crypto/RoomCryptoService.kt | 4 +--- .../sdk/internal/session/room/DefaultRoom.kt | 12 ++++-------- .../settings/RoomSettingsViewModel.kt | 16 +++++++--------- 4 files changed, 14 insertions(+), 22 deletions(-) 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 1a9165ade4..cbb22daf0f 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 @@ -68,8 +68,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { if (encryptedRoom) { val room = aliceSession.getRoom(roomId)!! - mTestHelper.doSync { - room.enableEncryption(callback = it) + mTestHelper.runBlockingTest { + room.enableEncryption() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt index e7e6bacc22..1251fd9857 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.room.crypto -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM interface RoomCryptoService { @@ -30,6 +29,5 @@ interface RoomCryptoService { /** * Enable encryption of the room */ - fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM, - callback: MatrixCallback) + suspend fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM) } 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 1338df6878..c7bb640f7c 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 @@ -101,13 +101,13 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, return cryptoService.shouldEncryptForInvitedMembers(roomId) } - override fun enableEncryption(algorithm: String, callback: MatrixCallback) { + override suspend fun enableEncryption(algorithm: String) { when { isEncrypted() -> { - callback.onFailure(IllegalStateException("Encryption is already enabled for this room")) + throw IllegalStateException("Encryption is already enabled for this room") } algorithm != MXCRYPTO_ALGORITHM_MEGOLM -> { - callback.onFailure(InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")) + throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported") } else -> { val params = SendStateTask.Params( @@ -118,11 +118,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, "algorithm" to algorithm )) - sendStateTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + sendStateTask.execute(params) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 4e540f867e..086ce93bb0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -27,7 +28,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import io.reactivex.Completable import io.reactivex.Observable -import org.matrix.android.sdk.api.MatrixCallback +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 @@ -228,16 +229,13 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: private fun handleEnableEncryption() { postLoading(true) - room.enableEncryption(callback = object : MatrixCallback { - override fun onFailure(failure: Throwable) { - postLoading(false) + viewModelScope.launch { + val result = runCatching { room.enableEncryption() } + postLoading(false) + result.onFailure { failure -> _viewEvents.post(RoomSettingsViewEvents.Failure(failure)) } - - override fun onSuccess(data: Unit) { - postLoading(false) - } - }) + } } private fun postLoading(isLoading: Boolean) { From ccf5d759a4c4a442d1045b48aa7cf05a850f2519 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Nov 2020 15:12:49 +0100 Subject: [PATCH 039/231] Add userConsent to the Identity database and migrate the DB --- .../session/identity/IdentityModule.kt | 4 ++ .../session/identity/data/IdentityData.kt | 3 +- .../session/identity/data/IdentityStore.kt | 2 + .../session/identity/db/IdentityDataEntity.kt | 3 +- .../session/identity/db/IdentityMapper.kt | 3 +- .../session/identity/db/RealmIdentityStore.kt | 8 ++++ .../db/RealmIdentityStoreMigration.kt | 43 +++++++++++++++++++ 7 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt index e140cc19f3..7a39a333a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.identity.db.IdentityRealmModule import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStore import io.realm.RealmConfiguration import okhttp3.OkHttpClient +import org.matrix.android.sdk.internal.session.identity.db.RealmIdentityStoreMigration import java.io.File @Module @@ -59,6 +60,7 @@ internal abstract class IdentityModule { @SessionScope fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, @SessionFilesDirectory directory: File, + migration: RealmIdentityStoreMigration, @UserMd5 userMd5: String): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) @@ -66,6 +68,8 @@ internal abstract class IdentityModule { .apply { realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } + .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION) + .migration(migration) .allowWritesOnUiThread(true) .modules(IdentityRealmModule()) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityData.kt index 0f04f2fe1a..54d35b34fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityData.kt @@ -20,5 +20,6 @@ internal data class IdentityData( val identityServerUrl: String?, val token: String?, val hashLookupPepper: String?, - val hashLookupAlgorithm: List + val hashLookupAlgorithm: List, + val userConsent: Boolean ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt index 3a905833d5..0e05224be5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/data/IdentityStore.kt @@ -27,6 +27,8 @@ internal interface IdentityStore { fun setToken(token: String?) + fun setUserConsent(consent: Boolean) + fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntity.kt index cc03465cc8..019289a884 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntity.kt @@ -23,7 +23,8 @@ internal open class IdentityDataEntity( var identityServerUrl: String? = null, var token: String? = null, var hashLookupPepper: String? = null, - var hashLookupAlgorithm: RealmList = RealmList() + var hashLookupAlgorithm: RealmList = RealmList(), + var userConsent: Boolean = false ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityMapper.kt index 98207f1b38..bf23c05811 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityMapper.kt @@ -26,7 +26,8 @@ internal object IdentityMapper { identityServerUrl = entity.identityServerUrl, token = entity.token, hashLookupPepper = entity.hashLookupPepper, - hashLookupAlgorithm = entity.hashLookupAlgorithm.toList() + hashLookupAlgorithm = entity.hashLookupAlgorithm.toList(), + userConsent = entity.userConsent ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStore.kt index 0352e9b936..2fa3fc0cfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStore.kt @@ -55,6 +55,14 @@ internal class RealmIdentityStore @Inject constructor( } } + override fun setUserConsent(consent: Boolean) { + Realm.getInstance(realmConfiguration).use { + it.executeTransaction { realm -> + IdentityDataEntity.setUserConsent(realm, consent) + } + } + } + override fun setHashDetails(hashDetailResponse: IdentityHashDetailResponse) { Realm.getInstance(realmConfiguration).use { it.executeTransaction { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt new file mode 100644 index 0000000000..6081dbab12 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.identity.db + +import io.realm.DynamicRealm +import io.realm.RealmMigration +import timber.log.Timber +import javax.inject.Inject + +internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { + + companion object { + const val IDENTITY_STORE_SCHEMA_VERSION = 1L + } + + override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { + Timber.v("Migrating Realm Identity from $oldVersion to $newVersion") + + if (oldVersion <= 0) migrateTo1(realm) + } + + private fun migrateTo1(realm: DynamicRealm) { + Timber.d("Step 0 -> 1") + Timber.d("Add field userConsent (Boolean) and set the value to false") + + realm.schema.get("IdentityDataEntity") + ?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java) + } +} From d1e2d065389e7aff3132458cb04aa1353121c38d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Nov 2020 16:07:18 +0100 Subject: [PATCH 040/231] Add userConsent UI to the Discovery screen --- .../api/session/identity/IdentityService.kt | 15 ++++++++ .../session/identity/IdentityServiceError.kt | 1 + .../identity/DefaultIdentityService.kt | 17 ++++++++++ .../identity/db/IdentityDataEntityQuery.kt | 7 ++++ .../vector/app/core/error/ErrorFormatter.kt | 1 + .../discovery/DiscoverySettingsAction.kt | 1 + .../discovery/DiscoverySettingsController.kt | 34 +++++++++++++++++++ .../discovery/DiscoverySettingsFragment.kt | 17 ++++++++++ .../discovery/DiscoverySettingsState.kt | 3 +- .../discovery/DiscoverySettingsViewModel.kt | 30 +++++++++++++--- vector/src/main/res/values/strings.xml | 9 +++++ 11 files changed, 130 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index 537104a084..908bbcff4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -92,9 +92,24 @@ interface IdentityService { /** * Search MatrixId of users providing email and phone numbers + * Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure + * Application has to explicitly ask for the user consent. + * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. */ fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable + /** + * Return the current user consent + */ + fun getUserConsent(): Boolean + + /** + * Set the user consent. Application may have explicitly ask for the user consent to send their private data + * (email and phone numbers) to the identity server. + * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. + */ + fun setUserConsent(newValue: Boolean) + /** * Get the status of the current user's threePid * A lookup will be performed, but also pending binding state will be restored diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityServiceError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityServiceError.kt index 72bb72cc2c..42fdb97643 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityServiceError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityServiceError.kt @@ -24,6 +24,7 @@ sealed class IdentityServiceError : Failure.FeatureFailure() { object NoIdentityServerConfigured : IdentityServiceError() object TermsNotSignedException : IdentityServiceError() object BulkLookupSha256NotSupported : IdentityServiceError() + object UserConsentNotProvided : IdentityServiceError() object BindingError : IdentityServiceError() object NoCurrentBindingError : IdentityServiceError() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 20f8b7f868..9e2eb72375 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureProtocol import kotlinx.coroutines.withContext import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -243,7 +244,20 @@ internal class DefaultIdentityService @Inject constructor( )) } + override fun getUserConsent(): Boolean { + return identityStore.getIdentityData()?.userConsent.orFalse() + } + + override fun setUserConsent(newValue: Boolean) { + identityStore.setUserConsent(newValue) + } + override fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable { + if (!getUserConsent()) { + callback.onFailure(IdentityServiceError.UserConsentNotProvided) + return NoOpCancellable + } + if (threePids.isEmpty()) { callback.onSuccess(emptyList()) return NoOpCancellable @@ -255,6 +269,9 @@ internal class DefaultIdentityService @Inject constructor( } override fun getShareStatus(threePids: List, callback: MatrixCallback>): Cancelable { + // Note: we do not require user consent here, because it is used for email and phone numbers that the user has already sent + // to the home server. Identity server is another service though... + if (threePids.isEmpty()) { callback.onSuccess(emptyMap()) return NoOpCancellable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt index 062c28ea55..5152e33743 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/IdentityDataEntityQuery.kt @@ -52,6 +52,13 @@ internal fun IdentityDataEntity.Companion.setToken(realm: Realm, } } +internal fun IdentityDataEntity.Companion.setUserConsent(realm: Realm, + newConsent: Boolean) { + get(realm)?.apply { + userConsent = newConsent + } +} + internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm, pepper: String, algorithms: List) { diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 6065c74541..b9bc935890 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -136,6 +136,7 @@ class DefaultErrorFormatter @Inject constructor( IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error + IdentityServiceError.UserConsentNotProvided -> R.string.identity_server_user_consent_not_provided }) } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt index c66ae69e6a..426f1321e7 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsAction.kt @@ -25,6 +25,7 @@ sealed class DiscoverySettingsAction : VectorViewModelAction { object DisconnectIdentityServer : DiscoverySettingsAction() data class ChangeIdentityServer(val url: String) : DiscoverySettingsAction() + data class UpdateUserConsent(val newConsent: Boolean) : DiscoverySettingsAction() data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction() data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction() data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction() diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt index 306d9bffd1..55c11f3a50 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsController.kt @@ -65,6 +65,7 @@ class DiscoverySettingsController @Inject constructor( buildIdentityServerSection(data) val hasIdentityServer = data.identityServer().isNullOrBlank().not() if (hasIdentityServer && !data.termsNotSigned) { + buildConsentSection(data) buildEmailsSection(data.emailList) buildMsisdnSection(data.phoneNumbersList) } @@ -72,6 +73,38 @@ class DiscoverySettingsController @Inject constructor( } } + private fun buildConsentSection(data: DiscoverySettingsState) { + settingsSectionTitleItem { + id("idConsentTitle") + titleResId(R.string.settings_discovery_consent_title) + } + + if (data.userConsent) { + settingsInfoItem { + id("idConsentInfo") + helperTextResId(R.string.settings_discovery_consent_notice_on) + } + settingsButtonItem { + id("idConsentButton") + colorProvider(colorProvider) + buttonTitleId(R.string.settings_discovery_consent_action_revoke) + buttonStyle(ButtonStyle.DESTRUCTIVE) + buttonClickListener { listener?.onTapUpdateUserConsent(false) } + } + } else { + settingsInfoItem { + id("idConsentInfo") + helperTextResId(R.string.settings_discovery_consent_notice_off) + } + settingsButtonItem { + id("idConsentButton") + colorProvider(colorProvider) + buttonTitleId(R.string.settings_discovery_consent_action_give_consent) + buttonClickListener { listener?.onTapUpdateUserConsent(true) } + } + } + } + private fun buildIdentityServerSection(data: DiscoverySettingsState) { val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none) @@ -359,6 +392,7 @@ class DiscoverySettingsController @Inject constructor( fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String) fun onTapChangeIdentityServer() fun onTapDisconnectIdentityServer() + fun onTapUpdateUserConsent(newValue: Boolean) fun onTapRetryToRetrieveBindings() } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt index bfbc00b15a..97d824054d 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt @@ -170,6 +170,23 @@ class DiscoverySettingsFragment @Inject constructor( } } + override fun onTapUpdateUserConsent(newValue: Boolean) { + if (newValue) { + withState(viewModel) { state -> + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.identity_server_consent_dialog_title) + .setMessage(getString(R.string.identity_server_consent_dialog_content, state.identityServer.invoke())) + .setPositiveButton(R.string.yes) { _, _ -> + viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(true)) + } + .setNegativeButton(R.string.no, null) + .show() + } + } else { + viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(false)) + } + } + override fun onTapRetryToRetrieveBindings() { viewModel.handle(DiscoverySettingsAction.RetrieveBinding) } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt index 6b28c07e89..21fbcf1ca7 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsState.kt @@ -25,5 +25,6 @@ data class DiscoverySettingsState( val emailList: Async> = Uninitialized, val phoneNumbersList: Async> = Uninitialized, // Can be true if terms are updated - val termsNotSigned: Boolean = false + val termsNotSigned: Boolean = false, + val userConsent: Boolean = false ) : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index 0bfcdd9984..0f294e080a 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -63,7 +63,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor( val identityServerUrl = identityService.getCurrentIdentityServerUrl() val currentIS = state.identityServer() setState { - copy(identityServer = Success(identityServerUrl)) + copy( + identityServer = Success(identityServerUrl), + userConsent = false + ) } if (currentIS != identityServerUrl) retrieveBinding() } @@ -71,7 +74,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor( init { setState { - copy(identityServer = Success(identityService.getCurrentIdentityServerUrl())) + copy( + identityServer = Success(identityService.getCurrentIdentityServerUrl()), + userConsent = identityService.getUserConsent() + ) } startListenToIdentityManager() observeThreePids() @@ -97,6 +103,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( DiscoverySettingsAction.RetrieveBinding -> retrieveBinding() DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer() is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action) + is DiscoverySettingsAction.UpdateUserConsent -> handleUpdateUserConsent(action) is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action) is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action) is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true) @@ -105,13 +112,23 @@ class DiscoverySettingsViewModel @AssistedInject constructor( }.exhaustive } + private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) { + identityService.setUserConsent(action.newConsent) + setState { copy(userConsent = action.newConsent) } + } + private fun disconnectIdentityServer() { setState { copy(identityServer = Loading()) } viewModelScope.launch { try { awaitCallback { session.identityService().disconnect(it) } - setState { copy(identityServer = Success(null)) } + setState { + copy( + identityServer = Success(null), + userConsent = false + ) + } } catch (failure: Throwable) { setState { copy(identityServer = Fail(failure)) } } @@ -126,7 +143,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor( val data = awaitCallback { session.identityService().setNewIdentityServer(action.url, it) } - setState { copy(identityServer = Success(data)) } + setState { + copy( + identityServer = Success(data), + userConsent = false + ) + } retrieveBinding() } catch (failure: Throwable) { setState { copy(identityServer = Fail(failure)) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 45d9d40ba6..4f751a68d0 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1793,6 +1793,14 @@ We sent you a confirm email to %s, check your email and click on the confirmation link We sent you a confirm email to %s, please first check your email and click on the confirmation link Pending + Send emails and phone numbers + You have given your consent to send emails and phone numbers to this identity server to discover other users from your contacts. + You have not given your consent to send emails and phone numbers to this identity server to discover other users from your contacts. + Revoke my consent + Give consent + + Send emails and phone numbers + In order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured Identity Server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent. Enter an identity server URL Could not connect to identity server @@ -2527,6 +2535,7 @@ For your privacy, Element only supports sending hashed user emails and phone number. The association has failed. The is no current association with this identifier. + The user consent has not been provided. Your homeserver (%1$s) proposes to use %2$s for your identity server Use %1$s From 99bea8f7c321adf493660c61d6d5be566c39f47d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Nov 2020 16:40:33 +0100 Subject: [PATCH 041/231] small change in signature --- .../features/contactsbook/ContactsBookController.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookController.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookController.kt index 9eca2afa60..59c23f4ac7 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookController.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookController.kt @@ -52,11 +52,10 @@ class ContactsBookController @Inject constructor( override fun buildModels() { val currentState = state ?: return - val hasSearch = currentState.searchTerm.isNotEmpty() when (val asyncMappedContacts = currentState.mappedContacts) { is Uninitialized -> renderEmptyState(false) is Loading -> renderLoading() - is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch, currentState.onlyBoundContacts) + is Success -> renderSuccess(currentState) is Fail -> renderFailure(asyncMappedContacts.error) } } @@ -75,13 +74,13 @@ class ContactsBookController @Inject constructor( } } - private fun renderSuccess(mappedContacts: List, - hasSearch: Boolean, - onlyBoundContacts: Boolean) { + private fun renderSuccess(state: ContactsBookViewState) { + val mappedContacts = state.filteredMappedContacts + if (mappedContacts.isEmpty()) { - renderEmptyState(hasSearch) + renderEmptyState(state.searchTerm.isNotEmpty()) } else { - renderContacts(mappedContacts, onlyBoundContacts) + renderContacts(mappedContacts, state.onlyBoundContacts) } } From 6020f423f4920059fb612e7c5e7d8a55afc79b87 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 11 Nov 2020 17:12:42 +0100 Subject: [PATCH 042/231] Ask for explicit user consent to send their contact details to the identity server (#2375) --- CHANGES.md | 1 + .../contactsbook/ContactsBookAction.kt | 1 + .../contactsbook/ContactsBookFragment.kt | 18 +++++++++++ .../contactsbook/ContactsBookViewModel.kt | 30 ++++++++++++++++--- .../contactsbook/ContactsBookViewState.kt | 8 +++-- .../res/layout/fragment_contacts_book.xml | 23 +++++++++++++- vector/src/main/res/values/strings.xml | 1 + 7 files changed, 75 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4a6133c103..c2c287eb20 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Open an existing DM instead of creating a new one (#2319) + - Ask for explicit user consent to send their contact details to the identity server (#2375) Bugfix 🐛: - Fix issue when restoring draft after sharing (#2287) diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookAction.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookAction.kt index 8eb5bc733b..e380998fd2 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookAction.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookAction.kt @@ -21,4 +21,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class ContactsBookAction : VectorViewModelAction { data class FilterWith(val filter: String) : ContactsBookAction() data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : ContactsBookAction() + object UserConsentGranted : ContactsBookAction() } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt index c4cf9eab39..23d21f5240 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt @@ -18,6 +18,7 @@ package im.vector.app.features.contactsbook import android.os.Bundle import android.view.View +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState @@ -57,10 +58,26 @@ class ContactsBookFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) setupRecyclerView() setupFilterView() + setupConsentView() setupOnlyBoundContactsView() setupCloseView() } + private fun setupConsentView() { + phoneBookSearchForMatrixContacts.setOnClickListener { + withState(contactsBookViewModel) { state -> + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.identity_server_consent_dialog_title) + .setMessage(getString(R.string.identity_server_consent_dialog_content, state.identityServerUrl ?: "")) + .setPositiveButton(R.string.yes) { _, _ -> + contactsBookViewModel.handle(ContactsBookAction.UserConsentGranted) + } + .setNegativeButton(R.string.no, null) + .show() + } + } + } + private fun setupOnlyBoundContactsView() { phoneBookOnlyBoundContacts.checkedChanges() .subscribe { @@ -98,6 +115,7 @@ class ContactsBookFragment @Inject constructor( } override fun invalidate() = withState(contactsBookViewModel) { state -> + phoneBookSearchForMatrixContacts.isVisible = state.filteredMappedContacts.isNotEmpty() && state.identityServerUrl != null && !state.userConsent phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved contactsBookController.setData(state) } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt index 167660d11e..2c4c5d0596 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt @@ -38,11 +38,10 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.FoundThreePid +import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import timber.log.Timber -private typealias PhoneBookSearch = String - class ContactsBookViewModel @AssistedInject constructor(@Assisted initialState: ContactsBookViewState, private val contactsDataSource: ContactsDataSource, @@ -85,7 +84,9 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted private fun loadContacts() { setState { copy( - mappedContacts = Loading() + mappedContacts = Loading(), + identityServerUrl = session.identityService().getCurrentIdentityServerUrl(), + userConsent = session.identityService().getUserConsent() ) } @@ -109,6 +110,9 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted } private fun performLookup(data: List) { + if (!session.identityService().getUserConsent()) { + return + } viewModelScope.launch { val threePids = data.flatMap { contact -> contact.emails.map { ThreePid.Email(it.email) } + @@ -116,8 +120,14 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted } session.identityService().lookUp(threePids, object : MatrixCallback> { override fun onFailure(failure: Throwable) { - // Ignore Timber.w(failure, "Unable to perform the lookup") + + // Should not happen, but just to be sure + if (failure is IdentityServiceError.UserConsentNotProvided) { + setState { + copy(userConsent = false) + } + } } override fun onSuccess(data: List) { @@ -171,9 +181,21 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted when (action) { is ContactsBookAction.FilterWith -> handleFilterWith(action) is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action) + ContactsBookAction.UserConsentGranted -> handleUserConsentGranted() }.exhaustive } + private fun handleUserConsentGranted() { + session.identityService().setUserConsent(true) + + setState { + copy(userConsent = true) + } + + // Perform the lookup + performLookup(allContacts) + } + private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) { setState { copy( diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt index 3e4f4ddcb6..d2ee684c4d 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewState.kt @@ -26,10 +26,14 @@ data class ContactsBookViewState( val mappedContacts: Async> = Loading(), // Use to filter contacts by display name val searchTerm: String = "", - // Tru to display only bound contacts with their bound 2pid + // True to display only bound contacts with their bound 2pid val onlyBoundContacts: Boolean = false, // All contacts, filtered by searchTerm and onlyBoundContacts val filteredMappedContacts: List = emptyList(), // True when the identity service has return some data - val isBoundRetrieved: Boolean = false + val isBoundRetrieved: Boolean = false, + // The current identity server url if any + val identityServerUrl: String? = null, + // User consent to perform lookup (send emails to the identity server) + val userConsent: Boolean = false ) : MvRxState diff --git a/vector/src/main/res/layout/fragment_contacts_book.xml b/vector/src/main/res/layout/fragment_contacts_book.xml index eb90da1bbe..1f8566e05e 100644 --- a/vector/src/main/res/layout/fragment_contacts_book.xml +++ b/vector/src/main/res/layout/fragment_contacts_book.xml @@ -93,6 +93,27 @@ app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer" tools:visibility="visible" /> + + + + + app:layout_constraintTop_toBottomOf="@+id/phoneBookBottomBarrier" /> Retrieving your contacts… Your contact book is empty Contacts book + Search for contacts on Matrix Revoke invite Revoke invite to %1$s? From 4dd83c29fe8c538fd2ceeed9ce66fd2513cfa0a6 Mon Sep 17 00:00:00 2001 From: Thomas sivertsen Date: Wed, 11 Nov 2020 18:03:06 +0000 Subject: [PATCH 043/231] =?UTF-8?q?Added=20translation=20using=20Weblate?= =?UTF-8?q?=20(Norwegian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/metadata/android/it/changelogs/40100100.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/it/changelogs/40100100.txt diff --git a/fastlane/metadata/android/it/changelogs/40100100.txt b/fastlane/metadata/android/it/changelogs/40100100.txt new file mode 100644 index 0000000000..0c7cc8cc6c --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/40100100.txt @@ -0,0 +1 @@ +// DA FARE From 8e9e4215ce31e34dd09a3489f64d0c3ee896d356 Mon Sep 17 00:00:00 2001 From: Thomas sivertsen Date: Wed, 11 Nov 2020 18:04:15 +0000 Subject: [PATCH 044/231] =?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 50.0% (2 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/nb/ --- fastlane/metadata/android/nb/short_description.txt | 1 + fastlane/metadata/android/nb/title.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/nb/short_description.txt create mode 100644 fastlane/metadata/android/nb/title.txt diff --git a/fastlane/metadata/android/nb/short_description.txt b/fastlane/metadata/android/nb/short_description.txt new file mode 100644 index 0000000000..b7cad4c849 --- /dev/null +++ b/fastlane/metadata/android/nb/short_description.txt @@ -0,0 +1 @@ +Sikker desentralisert chat & VoIP. Beskytt dataene dine fra tredjeparter. diff --git a/fastlane/metadata/android/nb/title.txt b/fastlane/metadata/android/nb/title.txt new file mode 100644 index 0000000000..aacee5be54 --- /dev/null +++ b/fastlane/metadata/android/nb/title.txt @@ -0,0 +1 @@ +Element (tidligere Riot.im) From daac2e2a1c1f508bb342bbbc61861f4e046adf27 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 12 Nov 2020 17:21:48 +0100 Subject: [PATCH 045/231] Better rational --- .../sdk/internal/session/identity/DefaultIdentityService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 9e2eb72375..c6fb34151c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -269,8 +269,8 @@ internal class DefaultIdentityService @Inject constructor( } override fun getShareStatus(threePids: List, callback: MatrixCallback>): Cancelable { - // Note: we do not require user consent here, because it is used for email and phone numbers that the user has already sent - // to the home server. Identity server is another service though... + // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent + // to the home server, and not emails and phone numbers from the contact book of the user if (threePids.isEmpty()) { callback.onSuccess(emptyMap()) From fe40e74809a01c182995ed535ff5d2f203934fa8 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Tue, 10 Nov 2020 22:49:07 +0000 Subject: [PATCH 046/231] Translated using Weblate (Catalan) Currently translated at 60.1% (1162 of 1933 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 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 1fccac81d5..91f4fad19c 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -1352,4 +1352,17 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Envia l\'historial de sol·licituds de compartició de claus Revisa Reprodueix + Activa l\'HD + Desactiva l\'HD + Posterior + Frontal + Commuta la càmera + Auriculars sense fils + Auriculars + Altaveu + Mòbil + Selecciona el dispositiu d\'àudio + No m\'ho tornis a preguntar + Prova fent servir %s + La crida ha fallat per culpa d\'una mala configuració del servidor \ No newline at end of file From 7ca57d918ed21082e47ea6c01f105e15de92b8d6 Mon Sep 17 00:00:00 2001 From: Hivaa Date: Wed, 11 Nov 2020 15:28:09 +0000 Subject: [PATCH 047/231] Translated using Weblate (Persian) Currently translated at 98.2% (1899 of 1933 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index e22d517789..8188cbf2c6 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -1100,7 +1100,7 @@ موفق! بازگشت به ورود هشدار - تنظیم نشانی رایانامه + ایمیل خود را وارد کنید رایانامه رایانامه (اختیاری) بعدی From 1a191c7d51ef661dd6ee239e91b16f3058f174b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20FERREIRA=20DE=20SOUSA?= Date: Mon, 9 Nov 2020 21:17:30 +0000 Subject: [PATCH 048/231] Translated using Weblate (French) Currently translated at 99.9% (1932 of 1933 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 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 50793add88..e0ef74d067 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2195,4 +2195,7 @@ Sujet Sujet du salon (facultatif) Nom du salon + Message direct + Inclure l\'historique d\'échange de clés + Plus aucun résultat \ No newline at end of file From 9df002fe4ec0e2d0f30eda6533207783c650dece Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 9 Nov 2020 20:36:56 +0000 Subject: [PATCH 049/231] Translated using Weblate (French) Currently translated at 99.9% (1932 of 1933 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index e0ef74d067..573ec93f87 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -25,7 +25,7 @@ Appel en cours Téléconférence en cours. \nLa rejoindre en %1$s ou en %2$s - vocal + Audio vidéo Impossible d’initier l’appel, réessayez plus tard En raison de permissions manquantes, certaines fonctionnalités peuvent être absentes… From cf2ea0f51cdc2009b73cdea0aca7801a82c58d1e Mon Sep 17 00:00:00 2001 From: OLIVIER Thomas Date: Mon, 9 Nov 2020 20:34:49 +0000 Subject: [PATCH 050/231] Translated using Weblate (French) Currently translated at 99.9% (1932 of 1933 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 573ec93f87..e4ec77068c 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2037,7 +2037,7 @@ Affichage de la notification Vous voyez la notification ! Cliquez-moi dessus ! La notification a été cliquée ! - Échec de l\'appel Element + Appel échoué Les messages de ce salon sont chiffrés de bout en bout. Vous avez créé et configuré ce salon. Confirmer le code pour le désactiver From ba5c42cc51ae9e9630dcb0a23bda9835574e72a4 Mon Sep 17 00:00:00 2001 From: notramo Date: Thu, 12 Nov 2020 18:49:34 +0000 Subject: [PATCH 051/231] Translated using Weblate (Hungarian) Currently translated at 94.9% (1835 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index d9e29c37bf..2b1247d7e3 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -424,7 +424,7 @@ Vedd figyelembe, hogy az alkalmazás újraindul ami sok időt vehet igénybe."< Alacsony prioritású Semmi Hozzáférés és láthatóság - Listázza ezt a szobát a szobák könyvtárba + Listázza ezt a szobát a szoba listában Szoba hozzáfárés Szoba előzmény olvashatósága Ki tud előzményt olvasni? From bf2a7c2efdea2afa71da13e1e6c74f1e83634b36 Mon Sep 17 00:00:00 2001 From: Kaede Date: Wed, 11 Nov 2020 13:36:48 +0000 Subject: [PATCH 052/231] Translated using Weblate (Japanese) Currently translated at 48.6% (941 of 1933 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 | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index 6a48529dbe..e0bfdb930d 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -806,7 +806,7 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 畳む メッセージとエラーの場合 とにかく通話する - 受け取る + 了承 このホームサーバーの方針を閲覧し承認してください: 通話設定画面 着信にElementの既定の着信音を使う @@ -1045,4 +1045,19 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ 他の利用可能な言語 メッセージエディタ 環境設定 + このデバイスを設定 + セキュアバックアップをリセット + セキュアバックアップを設定 + 管理 + セキュアバックアップ + 部屋を作成中… + 招待されています + %s からの招待 + このセッションは正常に検証されました。 + 概ね完了しました。%s の画面にも同じシールドアイコンが表示されていますか? + 相手のユーザーのデバイスのコードをスキャンし、相互に安全性を検証します。 + 相手のコードをスキャン + スキャンできません + 断る + 検証リクエスト \ No newline at end of file From 44604c25098f04645043546c4c6321c24ddfde65 Mon Sep 17 00:00:00 2001 From: Deleted User Date: Wed, 11 Nov 2020 18:12:12 +0000 Subject: [PATCH 053/231] =?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 42.2% (816 of 1933 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 | 184 ++---------------- 1 file changed, 21 insertions(+), 163 deletions(-) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 48f9e87737..42731a17c6 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -1,9 +1,8 @@ - + nb NO Latn - Lyst tema Mørkt tema Svart tema @@ -18,7 +17,6 @@ Send et klistremerke Er du sikker\? Laster… - OK Lukk Lagre @@ -51,7 +49,6 @@ Ignorer Gjennomgang Avslå - Avslutt Handlinger Logg ut @@ -59,15 +56,12 @@ Lukk Kopiert til utklippstavle Slå av - Advarsel Feil - Hjem Folk Rom Samfunn - Invitasjoner Lavprioritet Ingen treff @@ -76,12 +70,9 @@ Inviter Samfunn Ingen grupper - Meld fra om en bug Fremgang (%s%%) - Les - Bli med i rommet Brukernavn Opprett konto @@ -89,10 +80,8 @@ Logg ut Identitetstjener-URL Søk - Ta et bilde Spill inn en video - Logg inn Opprett konto Send @@ -106,26 +95,21 @@ Glemt passord\? Jeg har verifisert E-postadressen min Vennligst skriv inn en gyldig URL - Original Store Medium Små - I går I dag - Informasjon Lagret JA NEI Fortsett - Fjern Bli med Forhåndsvisning Avvis - Synkroniserer … 1sek @@ -143,9 +127,7 @@ 1d %dd - Lag - Tilkoblet Frakoblet Rolig @@ -154,7 +136,6 @@ Opphev utestengelse Spark ut %1$s %2$s - Søk Filen ble ikke funnet Stol på @@ -164,10 +145,8 @@ Filer Instillinger BLE MED - Avbryt opplastning Avbryt nedlasting - Ingen treff ROM ROM @@ -184,14 +163,10 @@ E-post Telefon Åpne Innstillinger - Slå på - Slå på - Vanlig Varslingslyd - Versjon Opphavsrettighet Retningslinjer for personvern @@ -206,14 +181,12 @@ Analyser Send analytiske data Ja, jeg vil hjelpe til! - ID Offentlig navn Sist sett Autentisering Passord: Send - Logget inn som Identitetstjener Brukergrensesnitt @@ -224,7 +197,6 @@ Nytt passord Bekreft nytt passord Passordene er ikke like - Land Telefonnummer Kode @@ -236,20 +208,16 @@ 1 uke 1 måned For alltid - Emne Lavprioritet Ingen - Varsler Alle Bannlyste brukere - Avansert Adresser Mappe Tema - Hendelsesinformasjon Bruker-ID Algoritme @@ -261,14 +229,11 @@ Eksporter Importer Svartelistet - ingen - Bekreft Svarteliste Hjemmetjener-URL Skriv her … - Rom Meg Skriftstørrelse @@ -278,40 +243,31 @@ Større Det største Enorm - Åpne i nettleser Din bruker-ID Ditt tema Modul-ID Rom-ID - - Tillat Bekreft Del Ignorer - Av Lag Eksempel eksempel - Hjem Folk Ble med Invitert Bli med igjen Glem rommet - Profilbilde - Deaktiver kontoen Deaktiver kontoen - Vennligst skriv inn et brukernavn. utvid skjul - Alltid %1$s: %1$s: %2$s @@ -321,7 +277,6 @@ Lagre som fil Erstatt Stopp - Uventet feil Er du sikker\? Aldri mist krypterte beskjeder @@ -329,23 +284,17 @@ Versjon Algoritme Signatur - Verifisert! Jeg forstår - Verifiseringsforespørsel Ukjent feil - Rediger Svar - Prøv igjen Invitert av %s - Reaksjoner Liker Reaksjoner - Endre Vennligst vent … Nytt rom @@ -355,20 +304,14 @@ Sikkerhet og personvern Ekspert Format: - Stemme og video Hjelp/Om - - Vis skjulte hendelser i tidslinjen - Venter … (redigert) - Vilkår for bruk Bytt ut identitetstjener Venter - Opprett et nytt rom Vis passord Skjul passord @@ -385,54 +328,41 @@ Registrer deg Logg inn Fortsett med SSO - Neste E-post Nytt passord - Fortsett - Jeg har verifisert E-postadressen min - Suksess! Passordet ditt har blitt tilbakestilt. Advarsel E-post E-post (valgfritt) Neste - Telefonnummer Neste - Skriv inn kode Neste - Brukernavn Passord Neste Advarsel Vennligst sjekk eposten din Sett av - Logg på Logg på Passord Instillinger Gjeldende økt Føyer til ¯\\_(ツ)_/¯ på en råtekstmelding - Video. Bilde. Lyd Fil - Du avbrøt Du aksepterte Verifiseringsforespørsel - - Du - Verifiser %s Verifiserte %s Sikkerhet @@ -444,24 +374,18 @@ Tilpasset Invitasjoner Brukere - Opphev ignorering - Tidslinje - Vil du skru på kryptering\? Bekreft Advarsel - Sesjoner Ja Advarsel: Bekreft fjerning Status.im-tema - Lytter etter hendelser Verifiser økten - Aktiv samtale Send loggbøker Send tilbakestillings-E-post @@ -474,7 +398,6 @@ %d medlemmer 1 medlem - %1$s nå %s skriver … Søk @@ -490,15 +413,12 @@ Element samler inn anonyme statistikker for å hjelpe oss med å forbedre programmet. %1$s @ %2$s Integreringsbehandler - Rommets navn Hvem kan lese historikken\? Hvem kan gå inn i dette rommet\? - Kun medlemmer (f.o.m. da denne innstillingen ble valgt) Kun medlemmer (f.o.m. da de ble invitert) Kun medlemmer (f.o.m. de ble med) - Kun folk som har blitt invitert Dette rommet viser ikke merkeskilter for noen samfunn Kopier rommets ID @@ -513,7 +433,6 @@ 1 rom %d rom - %1$s: 1 melding %1$s: %2$d meldinger @@ -522,7 +441,6 @@ %d varsel %d varsler - Ny hendelse Nye meldinger Ny invitasjon @@ -531,8 +449,6 @@ 1 aktiv modul %d aktive moduler - - Modul Last inn modul Ditt visningsnavn @@ -545,21 +461,17 @@ Velg rommets tema Stille Bråkete - Kryptert melding - Opprett et samfunn Samfunnsnavn Samfunns-ID Rom Ingen brukere - Rom 1 medlem %d medlemmer - 1 rom %d rom @@ -571,57 +483,45 @@ Begynn å bruke Nøkkelsikkerhetskopiering Det var meg Begynn å bruke Nøkkelsikkerhetskopiering - Sendte deg en invitasjon Rom Opprett et nytt rom Rom Direktemeldinger - Rommets navn URL: Direktemeldinger - Meldingsredigeringer Filtrer samtaler … Opprett et nytt rom Blir med i rommet … - Identitetstjener Skriv inn en ny identitetstjener Send vedlegg - Klistremerke Det er søppelpost Alle meldinger Kun nevninger Forlat rommet Uleste meldinger - Kom i gang - Velg en tjener Tilbakestill passordet på %1$s Advarsel! Velg en E-postadresse Velg et telefonnummer Godkjenn vilkårene for å fortsette - Avanserte innstillinger Utviklermodus Dersom dette først har blitt skrudd på, kan kryptering aldri bli skrudd av. - De samsvarer Ikke sikker Venter … Verifiser denne økten QR-kodebilde - Forlat rommet Forlater rommet … - Dersom dette først har blitt skrudd på, kan kryptering aldri bli skrudd av. - Aktive økter Vis alle økter Behandle økter @@ -629,18 +529,13 @@ Verifisert Betrodd Ikke betrodd - QR-kode - Nei - Utviklerverktøy Ny innlogging - Fjern … Bråkete notifikasjoner Stille notifikasjoner - Samfunnsdetaljer Sikkerhetskopiering av nøkler Bruk sikkerhetskopiering av nøkler @@ -653,9 +548,7 @@ Bruk sikkerhetskopiering av nøkler Sikkerhetskopi Du kommer til å miste tilgang til dine enkrypterte meldinger med mindre du sikkerhetskopierer nøklene dine før du logger av. - Tredjepartslinsenser - Bli Snakk Se dekryptert kilde @@ -686,9 +579,7 @@ Filtrer folk Filtrer romnavn Filtrer samfunnsnavn - Systemadvarsler - Samtaler Lokal adressebok Brukerkatalog @@ -696,14 +587,12 @@ Ingen samtaler Du ga ikke Element tilgang til dine lokale kontakter Ingen identitetsserver konfigurert. - Romkatalog Ingen offentlige rom tilgjengelig 1 bruker %d brukere - Send kjæsjlogg Send skjermbilde Vennligst forklar feilen. Hva gjorde du\? Hva forventet du at skulle skje\? Hva skjedde i stedet\? @@ -713,7 +602,6 @@ Det ser ut som du rister på telefonen i frustrasjon. Har du list til å åpne feilrapporteringsskjermen\? Applikasjonen kræsjet sist gang. Har du lyst til å åpne kræsjskjermen\? Sinnarist for å rapportere feil - Feilrapport har blitt sendt feilrapporten feilet å sendes (%s) Send inn i @@ -721,17 +609,13 @@ Start ny chat Start lydsamtale Start videosamtale - Send lydopptak - Er du sikker på du vil starte en samtale med %s\? Er du sikker på du vil starte en lydsamtale\? Er du sikker på du vil starte en videosamtale\? Spill av Pause Avslå - - Du har ikke rettigheter til å starte en konferansesamtale i dette rommet Det pågår allerede en konferanse! Start videomøte @@ -743,12 +627,10 @@ Kunne ikke fjerne utvidelse Kopier Suksess - Varsler Oppringing feilet som følge av feilkonfigurert tjener Prøv med %s Ikke spør meg igjen - Element samtale feilet Velg lydenhet Telefon @@ -760,17 +642,14 @@ Bak Slå HD av Slå HD på - Send filer Send klistremerke Ta bilde eller video Du har ingen pakker med klistremerker slått på. \n \nLegge til noen nå\? - fortsett med… Dessverre, kunne ikke finne en passende ekstern applikasjon for å utføre denne handlingen. - Logg på med single sign-on E-post eller brukernavn Brukernavn @@ -797,7 +676,6 @@ Forlat rommet %1$s for %2$s siden - Direktemeldinger Forlat dette rommet Fjern fra dette rommet @@ -805,9 +683,7 @@ Utnevn til admin Ignorer bruker Ignorer - Opphev ignorering - Bannlys bruker "%1$s, " %1$s og %2$s @@ -818,7 +694,6 @@ 1 ny melding %d nye meldinger - Romdetaljer 1 valgt @@ -827,13 +702,11 @@ INVITERT MELDINGER FILER - Opprett et rom Bli med i rommet Bli med i et rom Betingelser og vilkår Personvernregler - Legg til E-postadresse Legg til telefonnummer Avanserte varselsinnstillinger @@ -841,7 +714,6 @@ Kontoinnstillinger. Start ved systemoppstart Skru av restriksjoner - Batterioptimalisering Redusert privatliv Skru på varsler for denne kontoen @@ -850,7 +722,6 @@ Start ved systemoppstart Betingelser og vilkår Tøm mediemellomlager - Brukerinnstillinger Ignorerte brukere Integreringer @@ -863,23 +734,19 @@ Behandle dine oppdagelsesinnstillinger. Gi tillatelse Velg et annet alternativ - Gi tillatelse - Datasparingsmodus Øktinformasjon Hjemmetjener Tillat integreringer Integreringer er skrudd av Velg språk - Verifisering avventer Denne E-postadressen ble ikke funnet. Oppdater passord Telefonverifisering Standardkomprimering Merket som: - Tilgang og synlighet Romtilgang Punkt-til-punkt-kryptering @@ -891,10 +758,7 @@ %1$s i %2$s %1$s: %2$s %1$s: %2$s %3$s - Aktive moduler - - Last inn modulen på nytt Bruk kameraet Bruk mikrofonen @@ -903,25 +767,20 @@ Vennligst skriv inn passordet ditt. Samtalen fortsetter her Kontakt administrator - Beklager, det oppstod en feil - Opprett en passordfrase Bekreft passordfrasen Skriv inn passordfrase Passordfrasen samsvarer ikke Vennligst skriv inn en passordfrase Passordfrasen er for svak - (Avansert) Velg passordfrase Suksess! Gjenopprettingsnøkkel Skriv inn gjenopprettingsnøkkelen - Gjenopprett fra sikkerhetskopi Slett sikkerhetskopien - Slett sikkerhetskopien Begynn verifisering Du bruker ikke noen identitetstjenere @@ -931,35 +790,24 @@ Vis fjernede meldinger Vis en stattholder for fjernede meldinger Alle samfunn - Rom-arkiv Matrix SDK-versjon Hurtigreaksjoner - Send inn et forslag Krypterer filen … Navn eller ID (#example:matrix.org) - Filtrer etter brukernavn eller ID … - Vis redigeringshistorikk - MEDIA FILER %1$s, kl. %2$s Det er upassende IGNORER BRUKER - Ignorer bruker - Spoiler Du ignorerer ikke noen brukere - Langklikk på et rom for å se flere innstillinger - - Tilpassede og avanserte innstillinger - Koble til %1$s Adresse Sjekk innboksen din @@ -972,19 +820,14 @@ Du er logget av Du er logget av Tøm alle data - Se alle mine økter Skru på kryptering De samsvarer ikke Klistremerke - Skru på kryptering - Oppdater - Din gjenopprettingsnøkkel Avslutt - Kryptering er skrudd på Kryptering er ikke skrudd på Medlinger som inneholder @room @@ -993,9 +836,7 @@ Når rom blir oppgradert Feilsøk %1$s (%2$s) - Velg et nytt kontopassord … - Ukryptert Legg til medlemmer Inviterer brukere … @@ -1009,14 +850,12 @@ Opphev demping av mikrofonen Stopp kameraet Start kameraet - Sett opp Rommets navn Emne Kan ikke dekryptere SKJØNNER LÆR MER - Telefonkatalogen din er tom Telefonkatalog Søk i mine kontakter @@ -1025,4 +864,23 @@ Skriv inn PIN-koden din Glemt PIN-koden\? Skru på PIN-kode - + Sjekk e-postadressen din for å fortsette registreringen + Dette telefonnummeret er allerede definert. + Denne e-postadressen er allerede definert. + Passordet er for kort (min 6) + Brukernavn kan bare inneholde bokstaver, tall, prikker, bindestrek og understreker + Feil brukernavn og/eller passord + Angi en e-post for kontogjenoppretting. Bruk senere e-post eller telefon for å være mulig å oppdage av folk som kjenner deg. + Angi en e-post for kontogjenoppretting. Bruk senere e-post eller telefon for å være mulig å oppdage av folk som kjenner deg. + Sett en telefon, og senere for å bli valgbar for personer som kjenner deg. + Angi en e-post for gjenoppretting av kontoen, og senere for å være mulig å oppdage for folk som kjenner deg. + Send historikk om forespørsel om nøkkelandel + Ingen flere resultater + Legg på + Avslå + Godta + Du har ikke tillatelse til å starte en samtale + Du har ikke tillatelse til å starte en samtale i dette rommet + Du har ikke tillatelse til å starte en konferansesamtale + Tilbakestill + \ No newline at end of file From 5b2f95d270199d2d10875370849f6087d37f5c5c Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Tue, 10 Nov 2020 20:23:47 +0000 Subject: [PATCH 054/231] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (1933 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 674ce19759..c040882e80 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2250,4 +2250,8 @@ Descrição Descrição da sala (opcional) Nome da sala + Enviar histórico de solicitações do compartilhamento de chaves + Não há mais resultados + Exportar auditoria + Enviar mensagem \ No newline at end of file From 413a55623e1b6ee9071f4515513fe04f47fb7eaa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 00:39:16 +0100 Subject: [PATCH 055/231] Handle events of type "m.room.server_acl" (#890) --- CHANGES.md | 1 + .../android/sdk/api/extensions/Strings.kt | 5 ++ .../sdk/api/session/events/model/EventType.kt | 1 + .../room/model/RoomServerAclContent.kt | 59 +++++++++++++++ .../src/main/res/values/strings.xml | 17 +++++ .../action/MessageActionsViewModel.kt | 1 + .../timeline/factory/TimelineItemFactory.kt | 1 + .../timeline/format/NoticeEventFormatter.kt | 74 +++++++++++++++++++ .../helper/TimelineDisplayableEvents.kt | 1 + 9 files changed, 160 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomServerAclContent.kt diff --git a/CHANGES.md b/CHANGES.md index 4a6133c103..9387ae161e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Open an existing DM instead of creating a new one (#2319) + - Handle events of type "m.room.server_acl" (#890) Bugfix 🐛: - Fix issue when restoring draft after sharing (#2287) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt index a17e65b8e0..e264843ea4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt @@ -22,3 +22,8 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { else -> "$prefix$this" } } + +/** + * Append a new line and then the provided string + */ +fun StringBuilder.appendNl(str: String) = append("\n").append(str) 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 82dea81a5b..0a7f3ff09f 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 @@ -56,6 +56,7 @@ object EventType { const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups" const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events" const val STATE_ROOM_ENCRYPTION = "m.room.encryption" + const val STATE_ROOM_SERVER_ACL = "m.room.server_acl" // Call Events const val CALL_INVITE = "m.call.invite" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomServerAclContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomServerAclContent.kt new file mode 100644 index 0000000000..92078054b7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomServerAclContent.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing the EventType.STATE_ROOM_SERVER_ACL state event content + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#m-room-server-acl + */ +@JsonClass(generateAdapter = true) +data class RoomServerAclContent( + /** + * True to allow server names that are IP address literals. False to deny. + * Defaults to true if missing or otherwise not a boolean. + * This is strongly recommended to be set to false as servers running with IP literal names are strongly + * discouraged in order to require legitimate homeservers to be backed by a valid registered domain name. + */ + @Json(name = "allow_ip_literals") + val allowIpLiterals: Boolean = true, + + /** + * The server names to allow in the room, excluding any port information. Wildcards may be used to cover + * a wider range of hosts, where * matches zero or more characters and ? matches exactly one character. + * + * This defaults to an empty list when not provided, effectively disallowing every server. + */ + @Json(name = "allow") + val allowList: List = emptyList(), + + /** + * The server names to disallow in the room, excluding any port information. Wildcards may be used to cover + * a wider range of hosts, where * matches zero or more characters and ? matches exactly one character. + * + * This defaults to an empty list when not provided. + */ + @Json(name = "deny") + val denyList: List = emptyList() + +) { + companion object { + const val ALL = "*" + } +} diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 27f083269f..023650e0af 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -72,6 +72,23 @@ You upgraded this room. %s upgraded here. You upgraded here. + %s set the server ACLs for this room: + You set the server ACLs for this room: + • Server matching %s are banned. + • Server matching %s are allowed. + • Server matching IP literals are allowed. + • Server matching IP literals are banned. + + %s changed the server ACLs for this room: + You changed the server ACLs for this room: + • Server matching %s are now banned. + • Server matching %s were removed from the ban list. + • Server matching %s are now allowed. + • Server matching %s were removed from the allowed list. + • Server matching IP literals are now allowed. + • Server matching IP literals are now banned. + No change. + 🎉 All servers are banned from participating! This room can no longer be used. %1$s requested a VoIP conference You requested a VoIP conference diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 0d1e2261cd..8b0b905805 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -186,6 +186,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.STATE_ROOM_ALIASES, EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_SERVER_ACL, EventType.CALL_INVITE, EventType.CALL_CANDIDATES, EventType.CALL_HANGUP, 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 1a4db3bdfc..575f28b610 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 @@ -57,6 +57,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_JOIN_RULES, EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_GUEST_ACCESS, EventType.STATE_ROOM_WIDGET_LEGACY, EventType.STATE_ROOM_WIDGET, 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 8055ef9a99..c93f6b9837 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 @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.format import im.vector.app.ActiveSessionDataSource import im.vector.app.R import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.extensions.appendNl import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -35,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomServerAclContent import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent @@ -72,6 +74,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) + EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_WIDGET, @@ -383,6 +386,77 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } + private fun formatRoomServerAclEvent(event: Event, senderName: String?): String? { + val eventContent = event.getClearContent().toModel() ?: return null + val prevEventContent = event.resolvedPrevContent()?.toModel() + + return buildString { + // Title + append(if (prevEventContent == null) { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_server_acl_set_title_by_you) + } else { + sp.getString(R.string.notice_room_server_acl_set_title, senderName) + } + } else { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_server_acl_updated_title_by_you) + } else { + sp.getString(R.string.notice_room_server_acl_updated_title, senderName) + } + }) + // Details + if (eventContent.allowList.isEmpty()) { + // Special case for stuck room + append("\n") + append(sp.getString(R.string.notice_room_server_acl_allow_is_empty)) + } else { + if (prevEventContent == null) { + eventContent.allowList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_allowed, it)) } + eventContent.denyList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_banned, it)) } + if (eventContent.allowIpLiterals) { + appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_allowed)) + } else { + appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_not_allowed)) + } + } else { + // Display only diff + var hasChanged = false + // New allowed servers + (eventContent.allowList - prevEventContent.allowList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_allowed, it)) } + // Removed allowed servers + (prevEventContent.allowList - eventContent.allowList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_allowed, it)) } + // New denied servers + (eventContent.denyList - prevEventContent.denyList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_banned, it)) } + // Removed denied servers + (prevEventContent.denyList - eventContent.denyList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_banned, it)) } + + + if (prevEventContent.allowIpLiterals != eventContent.allowIpLiterals) { + hasChanged = true + if (eventContent.allowIpLiterals) { + appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_allowed)) + } else { + appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_not_allowed)) + } + } + + if (!hasChanged) { + appendNl(sp.getString(R.string.notice_room_server_acl_updated_no_change)) + } + } + } + } + } + private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? { val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel() val canonicalAlias = eventContent?.canonicalAlias diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 14b8c12fee..4fcac6c7f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -33,6 +33,7 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_ALIASES, EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_POWER_LEVELS, EventType.CALL_INVITE, EventType.CALL_HANGUP, From 60ce351a27c95cc9e0b2e5aeb71345458273ac49 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 12:44:46 +0000 Subject: [PATCH 056/231] Convert RoomPushRuleService to suspend functions Signed-off-by: Dominic Fischer --- .../room/notification/RoomPushRuleService.kt | 4 +--- .../notification/DefaultRoomPushRuleService.kt | 15 +++------------ .../features/home/room/list/RoomListViewModel.kt | 14 ++++++++++---- .../features/roomprofile/RoomProfileViewModel.kt | 8 +++++--- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomPushRuleService.kt index 32d6033578..eb822c68ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/notification/RoomPushRuleService.kt @@ -17,12 +17,10 @@ package org.matrix.android.sdk.api.session.room.notification import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable interface RoomPushRuleService { fun getLiveRoomNotificationState(): LiveData - fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback): Cancelable + suspend fun setRoomNotificationState(roomNotificationState: RoomNotificationState) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt index 8797b0c764..67ae55c066 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt @@ -21,21 +21,16 @@ import androidx.lifecycle.Transformations import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String, private val setRoomNotificationStateTask: SetRoomNotificationStateTask, - @SessionDatabase private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor) + @SessionDatabase private val monarchy: Monarchy) : RoomPushRuleService { @AssistedInject.Factory @@ -49,12 +44,8 @@ internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted } } - override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback): Cancelable { - return setRoomNotificationStateTask - .configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) { - this.callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun setRoomNotificationState(roomNotificationState: RoomNotificationState) { + setRoomNotificationStateTask.execute(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) } private fun getPushRuleForRoom(): LiveData { 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 c32629d6ae..b3af3b5e95 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 @@ -36,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber +import java.lang.Exception import javax.inject.Inject class RoomListViewModel @Inject constructor(initialState: RoomListViewState, @@ -169,11 +170,16 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) { - session.getRoom(action.roomId)?.setRoomNotificationState(action.notificationState, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomListViewEvents.Failure(failure)) + val room = session.getRoom(action.roomId) + if (room != null) { + viewModelScope.launch { + try { + room.setRoomNotificationState(action.notificationState) + } catch (failure: Exception) { + _viewEvents.post(RoomListViewEvents.Failure(failure)) + } } - }) + } } private fun handleToggleTag(action: RoomListAction.ToggleTag) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index e927ec9876..a78bf472d6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -102,11 +102,13 @@ class RoomProfileViewModel @AssistedInject constructor( } private fun handleChangeNotificationMode(action: RoomProfileAction.ChangeRoomNotificationState) { - room.setRoomNotificationState(action.notificationState, object : MatrixCallback { - override fun onFailure(failure: Throwable) { + viewModelScope.launch { + try { + room.setRoomNotificationState(action.notificationState) + } catch (failure: Throwable) { _viewEvents.post(RoomProfileViewEvents.Failure(failure)) } - }) + } } private fun handleLeaveRoom() { From b99cdf736703f1aa65c4ad7e462bd9c0baff1ce7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 11:07:50 +0100 Subject: [PATCH 057/231] Handle events of type "m.room.server_acl" - details only in developer mode (#890) --- .../src/main/res/values/strings.xml | 8 +- .../timeline/format/NoticeEventFormatter.kt | 99 ++++++++++--------- 2 files changed, 57 insertions(+), 50 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 023650e0af..de30a64c32 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -72,15 +72,15 @@ You upgraded this room. %s upgraded here. You upgraded here. - %s set the server ACLs for this room: - You set the server ACLs for this room: + %s set the server ACLs for this room. + You set the server ACLs for this room. • Server matching %s are banned. • Server matching %s are allowed. • Server matching IP literals are allowed. • Server matching IP literals are banned. - %s changed the server ACLs for this room: - You changed the server ACLs for this room: + %s changed the server ACLs for this room. + You changed the server ACLs for this room. • Server matching %s are now banned. • Server matching %s were removed from the ban list. • Server matching %s are now allowed. 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 c93f6b9837..b5859ba1ba 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 @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.format import im.vector.app.ActiveSessionDataSource import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.extensions.appendNl import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event @@ -50,9 +51,12 @@ import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent import timber.log.Timber import javax.inject.Inject -class NoticeEventFormatter @Inject constructor(private val activeSessionDataSource: ActiveSessionDataSource, - private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter, - private val sp: StringProvider) { +class NoticeEventFormatter @Inject constructor( + private val activeSessionDataSource: ActiveSessionDataSource, + private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter, + private val vectorPreferences: VectorPreferences, + private val sp: StringProvider +) { private val currentUserId: String? get() = activeSessionDataSource.currentValue?.orNull()?.myUserId @@ -405,55 +409,58 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour sp.getString(R.string.notice_room_server_acl_updated_title, senderName) } }) - // Details if (eventContent.allowList.isEmpty()) { // Special case for stuck room - append("\n") - append(sp.getString(R.string.notice_room_server_acl_allow_is_empty)) + appendNl(sp.getString(R.string.notice_room_server_acl_allow_is_empty)) + } else if (vectorPreferences.developerMode()) { + // Details, only in developer mode + appendAclDetails(eventContent, prevEventContent) + } + } + } + + private fun StringBuilder.appendAclDetails(eventContent: RoomServerAclContent, prevEventContent: RoomServerAclContent?) { + if (prevEventContent == null) { + eventContent.allowList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_allowed, it)) } + eventContent.denyList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_banned, it)) } + if (eventContent.allowIpLiterals) { + appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_allowed)) } else { - if (prevEventContent == null) { - eventContent.allowList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_allowed, it)) } - eventContent.denyList.forEach { appendNl(sp.getString(R.string.notice_room_server_acl_set_banned, it)) } - if (eventContent.allowIpLiterals) { - appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_allowed)) - } else { - appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_not_allowed)) - } + appendNl(sp.getString(R.string.notice_room_server_acl_set_ip_literals_not_allowed)) + } + } else { + // Display only diff + var hasChanged = false + // New allowed servers + (eventContent.allowList - prevEventContent.allowList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_allowed, it)) } + // Removed allowed servers + (prevEventContent.allowList - eventContent.allowList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_allowed, it)) } + // New denied servers + (eventContent.denyList - prevEventContent.denyList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_banned, it)) } + // Removed denied servers + (prevEventContent.denyList - eventContent.denyList) + .also { hasChanged = hasChanged || it.isNotEmpty() } + .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_banned, it)) } + + + if (prevEventContent.allowIpLiterals != eventContent.allowIpLiterals) { + hasChanged = true + if (eventContent.allowIpLiterals) { + appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_allowed)) } else { - // Display only diff - var hasChanged = false - // New allowed servers - (eventContent.allowList - prevEventContent.allowList) - .also { hasChanged = hasChanged || it.isNotEmpty() } - .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_allowed, it)) } - // Removed allowed servers - (prevEventContent.allowList - eventContent.allowList) - .also { hasChanged = hasChanged || it.isNotEmpty() } - .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_allowed, it)) } - // New denied servers - (eventContent.denyList - prevEventContent.denyList) - .also { hasChanged = hasChanged || it.isNotEmpty() } - .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_banned, it)) } - // Removed denied servers - (prevEventContent.denyList - eventContent.denyList) - .also { hasChanged = hasChanged || it.isNotEmpty() } - .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_banned, it)) } - - - if (prevEventContent.allowIpLiterals != eventContent.allowIpLiterals) { - hasChanged = true - if (eventContent.allowIpLiterals) { - appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_allowed)) - } else { - appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_not_allowed)) - } - } - - if (!hasChanged) { - appendNl(sp.getString(R.string.notice_room_server_acl_updated_no_change)) - } + appendNl(sp.getString(R.string.notice_room_server_acl_updated_ip_literals_not_allowed)) } } + + if (!hasChanged) { + appendNl(sp.getString(R.string.notice_room_server_acl_updated_no_change)) + } } } From b8c89325bc0e660e5f414a379cbd3b9264bd3651 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 11:25:18 +0100 Subject: [PATCH 058/231] Improve Javadoc --- .../sdk/api/session/identity/IdentityService.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index 908bbcff4a..aedb813735 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -93,20 +93,25 @@ interface IdentityService { /** * Search MatrixId of users providing email and phone numbers * Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure - * Application has to explicitly ask for the user consent. + * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent] * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. */ fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable /** - * Return the current user consent + * Return the current user consent for the current identity server, which has been stored using [setUserConsent]. + * If [setUserConsent] has not been called, the returned value will be false. + * Note that if the identity server is changed, the user consent is reset to false. + * @return the value stored using [setUserConsent] or false if [setUserConsent] has never been called, or if the identity server + * has been changed */ fun getUserConsent(): Boolean /** - * Set the user consent. Application may have explicitly ask for the user consent to send their private data + * Set the user consent to the provided value. Application MUST explicitly ask for the user consent to send their private data * (email and phone numbers) to the identity server. * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. + * @param newValue true if the user explicitly give their consent, false if the user wants to revoke their consent. */ fun setUserConsent(newValue: Boolean) From 8dff0b2c5dbd00f28882d8ab76cd2df218de8c52 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 15:33:44 +0100 Subject: [PATCH 059/231] Cleanup --- .../home/room/detail/timeline/format/NoticeEventFormatter.kt | 1 - 1 file changed, 1 deletion(-) 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 b5859ba1ba..c4cc2e87b0 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 @@ -448,7 +448,6 @@ class NoticeEventFormatter @Inject constructor( .also { hasChanged = hasChanged || it.isNotEmpty() } .forEach { appendNl(sp.getString(R.string.notice_room_server_acl_updated_was_banned, it)) } - if (prevEventContent.allowIpLiterals != eventContent.allowIpLiterals) { hasChanged = true if (eventContent.allowIpLiterals) { From 8ae0501c22d0c52832d196bc411af57c6314080c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 16:17:27 +0100 Subject: [PATCH 060/231] Try to fix cropped image in timeline (#2126) --- CHANGES.md | 1 + .../app/features/media/ImageContentRenderer.kt | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9387ae161e..8c0ea5e421 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Bugfix 🐛: - Fix issue when restoring draft after sharing (#2287) - Fix issue when updating the avatar of a room (new avatar vanishing) - Discard change dialog displayed by mistake when avatar has been updated + - Try to fix cropped image in timeline (#2126) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 4f1c52b240..187c2e85c3 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -21,6 +21,7 @@ import android.net.Uri import android.os.Parcelable import android.view.View import android.widget.ImageView +import androidx.core.view.updateLayoutParams import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.resource.bitmap.RoundedCorners @@ -96,15 +97,17 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: fun render(data: Data, mode: Mode, imageView: ImageView) { val size = processSize(data, mode) - imageView.layoutParams.width = size.width - imageView.layoutParams.height = size.height + imageView.updateLayoutParams { + width = size.width + height = size.height + } // a11y imageView.contentDescription = data.filename createGlideRequest(data, mode, imageView, size) .dontAnimate() .transform(RoundedCorners(dimensionConverter.dpToPx(8))) - .thumbnail(0.3f) + // .thumbnail(0.3f) .into(imageView) } @@ -117,6 +120,9 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: } } + /** + * Used by Attachment Viewer + */ fun render(data: Data, contextView: View, target: CustomViewTarget<*, Drawable>) { val req = if (data.elementToDecrypt != null) { // Encrypted image From f2e8a9e0c74ef55f704e3f63ae7cb822bbf59e4e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 18:25:04 +0100 Subject: [PATCH 061/231] Un configure the template, to be able to upgrade Android Studio --- tools/templates/configure.sh | 1 + tools/templates/unconfigure.sh | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100755 tools/templates/unconfigure.sh diff --git a/tools/templates/configure.sh b/tools/templates/configure.sh index 0669ab1312..1b00cef927 100755 --- a/tools/templates/configure.sh +++ b/tools/templates/configure.sh @@ -19,6 +19,7 @@ echo "Configure Element Template..." if [ -z ${ANDROID_STUDIO+x} ]; then ANDROID_STUDIO="/Applications/Android Studio.app/Contents"; fi { +mkdir -p "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other" ln -s $(pwd)/ElementFeature "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other" } && { echo "Please restart Android Studio." diff --git a/tools/templates/unconfigure.sh b/tools/templates/unconfigure.sh new file mode 100755 index 0000000000..36415c50e8 --- /dev/null +++ b/tools/templates/unconfigure.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# +# Copyright 2020 New Vector Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Template prevent from upgrading Android Studio, so this script de configure the template +echo "Un-configure Element Template..." +if [ -z ${ANDROID_STUDIO+x} ]; then ANDROID_STUDIO="/Applications/Android Studio.app/Contents"; fi + +rm "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other/ElementFeature" +rm -r "${ANDROID_STUDIO%/}/plugins/android/lib/templates" From 64c612dea0f386112c7644931c391e4e5153eaf3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Nov 2020 19:14:50 +0100 Subject: [PATCH 062/231] F-Droid version: ensure timeout of sync request can be more than 60 seconds (#2169) --- CHANGES.md | 1 + .../sdk/internal/network/TimeOutInterceptor.kt | 3 +++ .../android/sdk/internal/session/sync/SyncAPI.kt | 13 ++++++++----- .../android/sdk/internal/session/sync/SyncTask.kt | 12 +++++++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9387ae161e..416fa22015 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: - Handle events of type "m.room.server_acl" (#890) Bugfix 🐛: + - F-Droid version: ensure timeout of sync request can be more than 60 seconds (#2169) - Fix issue when restoring draft after sharing (#2287) - Fix issue when updating the avatar of a room (new avatar vanishing) - Discard change dialog displayed by mistake when avatar has been updated diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/TimeOutInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/TimeOutInterceptor.kt index 6c604f232f..724ec0dc7f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/TimeOutInterceptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/TimeOutInterceptor.kt @@ -52,5 +52,8 @@ internal class TimeOutInterceptor @Inject constructor() : Interceptor { const val CONNECT_TIMEOUT = "CONNECT_TIMEOUT" const val READ_TIMEOUT = "READ_TIMEOUT" const val WRITE_TIMEOUT = "WRITE_TIMEOUT" + + // 1 minute + const val DEFAULT_LONG_TIMEOUT: Long = 60_000 } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt index 427a8896c9..77289f04b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt @@ -17,18 +17,21 @@ package org.matrix.android.sdk.internal.session.sync import org.matrix.android.sdk.internal.network.NetworkConstants +import org.matrix.android.sdk.internal.network.TimeOutInterceptor import org.matrix.android.sdk.internal.session.sync.model.SyncResponse import retrofit2.Call import retrofit2.http.GET -import retrofit2.http.Headers +import retrofit2.http.Header import retrofit2.http.QueryMap internal interface SyncAPI { - /** - * Set all the timeouts to 1 minute + * Set all the timeouts to 1 minute by default */ - @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") - fun sync(@QueryMap params: Map): Call + fun sync(@QueryMap params: Map, + @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT + ): Call } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 303bb45419..b4fd6e7386 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.R import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.TimeOutInterceptor import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.filter.FilterRepository @@ -78,8 +79,13 @@ internal class DefaultSyncTask @Inject constructor( // Maybe refresh the home server capabilities data we know getHomeServerCapabilitiesTask.execute(Unit) + val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) + val syncResponse = executeRequest(eventBus) { - apiCall = syncAPI.sync(requestParams) + apiCall = syncAPI.sync( + params = requestParams, + readTimeOut = readTimeOut + ) } syncResponseHandler.handleResponse(syncResponse, token) if (isInitialSync) { @@ -87,4 +93,8 @@ internal class DefaultSyncTask @Inject constructor( } Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}") } + + companion object { + private const val TIMEOUT_MARGIN: Long = 10_000 + } } From 38a98a51cbf53c7f4ce473e97983a0f644b9b189 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Thu, 12 Nov 2020 20:24:55 +0000 Subject: [PATCH 063/231] Translated using Weblate (Catalan) Currently translated at 60.5% (1170 of 1933 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 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 91f4fad19c..7e88fb1a45 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -1365,4 +1365,12 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men No m\'ho tornis a preguntar Prova fent servir %s La crida ha fallat per culpa d\'una mala configuració del servidor + Correu electrònic + Confirma la teva contrasenya + Motiu del veto + Veta usuari + Cancel·la invitació + Cancel·la invitació + Torna a la trucada + Error SSL. \ No newline at end of file From b9375a1b4e74ef65fe2d4cc0b4fc6c6e119e020d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20B=C3=BCchner?= Date: Fri, 13 Nov 2020 12:49:18 +0000 Subject: [PATCH 064/231] Translated using Weblate (German) Currently translated at 100.0% (1933 of 1933 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 702e380e17..75ee23cb12 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2244,4 +2244,8 @@ Raumeinstellungen Raum-Thema (optional) Raumname + Prüfung exportieren + Direktnachricht + Geschichte der Anfragen von Schlüsselfreigaben senden + Keine weiteren Ergebnisse \ No newline at end of file From 13f716a3955c7eab85a1ab26f292f0e425156c5e Mon Sep 17 00:00:00 2001 From: Victor Cuadrado Juan Date: Fri, 13 Nov 2020 13:33:45 +0000 Subject: [PATCH 065/231] Translated using Weblate (Spanish) Currently translated at 97.3% (1881 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ --- vector/src/main/res/values-es/strings.xml | 622 +++------------------- 1 file changed, 79 insertions(+), 543 deletions(-) diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index c5ca0d5f1a..59237e8c5a 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -1,17 +1,15 @@ - + es ES - Mensajes Sala Ajustes Detalles de Miembro Histórico - Correcto Cancelar @@ -26,7 +24,7 @@ Reenviar Enlace Permanente Ver Fuente - Ver Fuente Descifrada + Ver Fuente Desencriptada Eliminar Renombrar Reportar contenido @@ -35,16 +33,15 @@ \nUnirse como %1$s o %2$s Voz Vídeo - No se puedo iniciar la llamada, por favor inténtelo de nuevo más tarde - Debido a que faltan permisos, pueden faltar algunas características… + No se puede iniciar la llamada, por favor inténtelo de nuevo más tarde + Debido a permisos insuficientes, pueden faltar algunas funciones… Necesitas permiso para invitar a iniciar una conferencia en esta sala No se puede iniciar la llamada Detalles de la sesión - No se admiten llamadas de conferencia en salas cifradas + No se admiten llamadas de conferencia en salas encriptadas Enviar de Todos Modos o Invitar - Cerrar sesión Llamada de Voz @@ -57,27 +54,22 @@ Cerrar Copiado al portapapeles Deshabilitar - Confirmación Advertencia - Inicio Favoritos Personas Salas - Filtrar salas Filtrar favoritos Filtrar personas Filtrar salas - Invitaciones Prioridad baja - Conversaciones Agenda de contactos local @@ -85,17 +77,15 @@ No hay conversaciones No permitiste que Element acceda a tus contactos locales No hay resultados - Salas Directorio de salas No hay salas No hay salas públicas disponibles - 1 usuario + %d usuario %d usuarios - Enviar registros Enviar registros de fallas Enviar captura de pantalla @@ -108,10 +98,8 @@ No se pudo enviar el informe de error (%s) Progreso (%s%%) La aplicación falló en la última sesión. ¿Te gustaría enviar un informe de error\? - Enviar en Leído - Unirse a la Sala Nombre de usuario Crear cuenta @@ -120,14 +108,11 @@ URL del Servidor Doméstico URL del Servidor de Identidad Buscar - Iniciar Nueva Conversación Iniciar Llamada de Voz Iniciar Llamada de Vídeo - Enviar archivos Tomar foto o vídeo - Iniciar sesión Crear cuenta @@ -176,7 +161,6 @@ Puedes añadir tu correo electrónico a tu perfil en ajustes. Tu contraseña fue restablecida. \n \nSe ha cerrado sesión en todas tus sesiones y ya no recibirás notificaciones push. Para volver a habilitar las notificaciones, vuelve a iniciar sesión en cada dispositivo. - La URL debe comenzar con http[s]:// No es posible iniciar sesión: Error de red @@ -185,7 +169,6 @@ Puedes añadir tu correo electrónico a tu perfil en ajustes. No es posible registrarse No es posible registrarse : falló la propiedad del correo electrónico Por favor introduce una URL válida - Nombre de usuario/contraseña inválidos No se reconoció el código de acceso especificado JSON mal formado @@ -193,36 +176,27 @@ Puedes añadir tu correo electrónico a tu perfil en ajustes. Se enviaron demasiadas solicitudes Este nombre de usuario ya está en uso El enlace del correo electrónico que aún no se ha seguido - - Lista de Recibos de Lectura - - Enviar como Original Grande Mediano Pequeño - "¿Cancelar la descarga? ¿Cancelar la subida? %d s %1$dm %2$ds - Ayer Hoy - - Nombre de la sala Tema de la sala - Llamada conectada Conectando llamada… @@ -232,21 +206,18 @@ Puedes añadir tu correo electrónico a tu perfil en ajustes. Llamada de Vídeo Entrante Llamada de Voz Entrante Llamada En Curso… - El lado remoto no contestó. Falló la Conexión de Medios No se puede iniciar la cámara llamada contestada en otra parte - Tomar una foto o un vídeo No se puede grabar vídeo - Información Element necesita permiso para acceder a tu biblioteca de fotos y vídeos para enviar y guardar archivos adjuntos. - -Por favor permite el acceso en la próxima ventana emergente para poder enviar archivos desde tu teléfono. +\n +\nPor favor permite el acceso en la próxima ventana emergente para poder enviar archivos desde tu teléfono. Element necesita permiso para acceder a tu cámara para tomar fotos y realizar llamadas de vídeo. " \n @@ -256,33 +227,28 @@ Por favor permite el acceso en la próxima ventana emergente para poder enviar a \n \nPor favor permite el acceso en la próxima ventana emergente para poder realizar la llamada." Element necesita permiso para acceder a tu cámara y micrófono para realizar llamadas de vídeo. - -Por favor permite el acceso en las próximas ventanas emergentes para poder realizar la llamada. +\n +\nPor favor permite el acceso en las próximas ventanas emergentes para poder realizar la llamada. Element necesita permiso para acceder a tu agenda de contactos para encontrar otros usuarios de Matrix por sus correos electrónicos y números telefónicos. Por favor permite el acceso en la próxima ventana emergente para descubrir usuarios accesibles desde Element en tu agenda de contactos. - Element necesita permiso para acceder a tu agenda de contactos para encontrar otros usuarios de Matrix por sus correos electrónicos y números telefónicos. - -¿Permitir que Element acceda a tus contactos ? - + Element necesita permiso para acceder a tu agenda de contactos para encontrar otros usuarios de Matrix por sus correos electrónicos y números telefónicos. +\n +\n¿Permitir que Element acceda a tus contactos \? Lo sentimos. Acción no realizada, debido a que faltan permisos - Guardado ¿Guardar en descargas? NO Continuar - Eliminar Unirse Vista Previa Rechazar - Ir al primer mensaje no leído. - Has sido invitado por %s a unirte a esta sala Esta invitación fue enviada a %s, que no esta asociado a esta cuenta. @@ -290,27 +256,22 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón Estás intentando acceder a %s. ¿Quieres unirte para participar en la discusión? una sala Esta es una vista previa de esta sala. Las interacciones dentro de la sala se han deshabilitado. - Nueva Conversación Añadir miembro 1 miembro - Salir de la sala ¿Seguro que quieres salir de la sala? ¿Seguro que quieres eliminar a %s de esta conversación? Crear - En línea Desconectado En reposo - HERRAMIENTAS DE ADMINISTRACIÓN LLAMAR CONVERSACIONES DIRECTAS SESIONES - Invitar Salir de esta sala Eliminar de esta sala @@ -326,18 +287,14 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón Mostrar Lista de Sesiones No podrás deshacer este cambio porque estás promoviendo al usuario para tener el mismo nivel de autoridad que tú. ¿Estás seguro? - ¿Seguro que quieres invitar a %s a esta conversación? - Invitar por ID CONTACTOS LOCALES (%d) Solo usuarios de Matrix - Invitar usuario por ID Por favor, ingresa una o más direcciones de correo electrónico o ID de Matrix Correo electrónico o ID de Matrix - Buscar %s está escribiendo… @@ -354,7 +311,6 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón Eliminar mensajes no enviados Archivo no encontrado No tienes permiso para publicar en esta sala - Confiar No confiar @@ -367,7 +323,6 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón El certificado cambió de uno que era confiable para tu teléfono. Esto es MUY INUSUAL. Se recomienda NO ACEPTAR este nuevo certificado. El certificado cambió de uno que era confiable a uno que no es confiable. El servidor puede haber renovado su certificado. Contacta al administrador del servidor para obtener la huella digital. Solo acepta el certificado si el administrador del servidor ha publicado una huella digital que coincide con la anterior. - Detalles de Sala Personas @@ -376,7 +331,6 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón ID mal formada. Debería ser una dirección de correo electrónico o una ID de Matrix como \'@partelocal:dominio\' INVITADOS SE UNIERON - Motivo para reportar este contenido ¿Quieres ocultar todos los mensajes de este usuario? @@ -384,7 +338,6 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de tiempo. Cancelar Subida Cancelar Descarga - Buscar Filtrar miembros de la sala @@ -393,7 +346,6 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de MENSAJES PERSONAS ARCHIVOS - UNIRSE DIRECTORIO @@ -406,18 +358,15 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Unirse a la sala Unirse a una sala Escribe una ID o alias de sala - Explorar directorio Buscando directorio… - Agregar a Favoritos Dejar de priorizar Conversación Directa Salir de la Conversación Olvidar - Mensajes Ajustes @@ -426,9 +375,7 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Avisos de terceros Derechos de autor Política de privacidad - - Imagen de Perfil Nombre Público Correo Electrónico @@ -437,22 +384,18 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Añadir número telefónico Mostrar la pantalla de información de la aplicación de los ajustes del sistema. Información de la aplicación - Habilitar notificaciones para esta cuenta Habilitar notificaciones para esta sesión Enciende la pantalla por 3 segundos - Mensajes en conversaciones uno a uno Mensajes en conversaciones en grupo Cuando soy invitado a una sala Invitaciones de llamada Mensajes enviados por bot - Sincronización en segundo plano Habilitar sincronización en segundo plano Venció el tiempo de espera para la solicitud de sincronización Retraso entre cada sincronización - Versión versión de olm Términos y condiciones @@ -461,8 +404,6 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Política de privacidad Borrar caché - - Ajustes de usuario Notificaciones Usuarios ignorados @@ -488,19 +429,15 @@ Para continuar, ingresa tu contraseña por favor. Autenticación Contraseña: Enviar - Sesión iniciada como Servidor Doméstico Servidor de Identidad - Verificación Pendiente Por favor, consulta tu correo electrónico y haz clic en el enlace que contiene. Una vez hecho esto, haz clic en continuar. No es posible verificar la dirección de correo electrónico. Por favor, consulta tu correo electrónico y haz clic en el enlace que contiene. Una vez hecho esto, haz clic en continuar. - Esta dirección de correo electrónico ya está en uso. No se encontró esta dirección de correo electrónico. Este número telefónico ya está en uso. - Cambiar contraseña Contraseña actual Contraseña nueva @@ -510,13 +447,9 @@ Para continuar, ingresa tu contraseña por favor. ¿Mostrar todos los mensajes de %s? Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de tiempo. - ¿Seguro que quieres eliminar este objetivo de notificaciones? - ¿Seguro que quieres eliminar los %1$s %2$s? - Elige un país - País Por favor, elige un país Número telefónico @@ -526,21 +459,17 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Ingresa un código de activación Error en la validación de tu número telefónico Código - - Imagen de Sala Nombre de Sala Tema Etiqueta de Sala Etiquetado como: - Agregar a Favoritos Prioridad baja Ninguno - Acceso y visibilidad Listar esta sala en el directorio de salas @@ -548,22 +477,18 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Legibilidad del Historial de la Sala ¿Quién puede leer el historial? ¿Quién puede acceder a esta sala? - Todos Solo miembros (desde el momento en que se selecciona esta opción) Solo miembros (desde que fueron invitados) Solo miembros (desde que se unieron) - Para crear un enlace a una sala, debe tener una dirección. Solo personas que han sido invitadas Cualquier persona que conozca el enlace a esta sala, excepto invitados Cualquier persona que conozca el enlace a esta sala, incluyendo invitados - Usuarios vetados - Avanzado La ID interna de esta sala @@ -575,35 +500,27 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Necesitas cerrar sesión para poder habilitar el cifrado. Cifrar solo a sesiones verificadas Nunca enviar mensajes cifrados a sesiones sin verificar en esta sala desde esta sesión. - Esta sala no tiene direcciones locales Dirección nueva (ej. #foo:matrix.org) - Formato de alias inválido \'%s\' no es un formato de alias válido No tendrás una dirección principal especificada para esta sala. Advertencias de la dirección principal - Establecer como Dirección Principal Dejar de Establecer como Dirección Principal Copiar ID de Sala Copiar Dirección de Sala - El cifrado está habilitado en esta sala. El cifrado está deshabilitado en esta sala. Habilitar cifrado (advertencia: ¡no se puede volver a deshabilitar!) - Directorio - %s estaba intentando cargar un momento específico en la línea de tiempo de esta sala pero no pudo encontrarlo. - Información de cifrado de extremo a extremo - Información de eventos ID de Usuario Clave de identidad Curve25519 @@ -611,7 +528,6 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Algoritmo ID de Sesión Error de descifrado - Información de la sesión emisora Nombre público Nombre público @@ -619,7 +535,6 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Clave de sesión Verificación Huella digital Ed25519 - Exportar claves de salas con Cifrado de Extremo a Extremo Exportar claves de sala Exportar las claves a un archivo local @@ -629,35 +544,28 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Las claves de salas con Cifrado de Extremo a Extremo se guardaron en \'%s\'. Advertencia: este archivo puede ser eliminado si la aplicación se desinstala. - Importar claves de salas con Cifrado de Extremo a Extremo Importar claves de sala Importar las claves desde un archivo local Importar Cifrar solo a sesiones verificadas Nunca enviar mensajes cifrados a sesiones sin verificar desde esta sesión. - SIN Verificar Verificado Prohibido - sesión desconocida ninguno - Verificar Anular Verificación Prohibir Dejar de Prohibir - Verificar sesión Para verificar que esta sesión es confiable, por favor contacta a su dueño por algún otro medio (ej. cara a cara o por teléfono) y pregúntale si la clave que ve en sus Ajustes de Usuario para esta sesión coincide con la clave a continuación: Si coincide, presione el botón de verificar a continuación. Si no coincide, entonces alguien está interceptando esta sesión y probablemente debería prohibirlo. En el futuro, este proceso de verificación será más sofisticado. Verifico que las claves coinciden - La sala contiene sesiones desconocidas Esta sala contiene sesiones desconocidas que no han sido verificados. Esto significa que no hay garantía de que las sesiones pertenezcan a los usuarios a los que dicen pertenecer. Recomendamos que pases por el proceso de verificación para cada sesión antes de continuar. Puedes reenviar el mensaje sin verificarlas si prefieres. Sesiones desconocidas: - Selecciona un directorio de salas El servidor puede estar no disponible o sobrecargado @@ -665,35 +573,27 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.URL del Servidor Doméstico Todas las salas en el servidor %s Todas las salas nativas de %s - Buscar en el historial Interfaz de usuario Idioma Elige idioma - Iniciar en el arranque Borrar caché de medios Guardar medios - Mostrar marcas temporales de todos los mensajes - 3 días 1 semana 1 mes Para siempre - Desconectado - Modo de ahorro de datos - Tamaño de letra Pequeño Normal Grande Mayor Tema - Diminuto Más Grande Enorme @@ -702,30 +602,23 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.Tema Claro Tema Oscuro Tema Negro - Sincronizando… - Detectar eventos + Captando eventos Notificaciones ruidosas Notificaciones silenciosas - Informe de error - Tomar foto Tomar vídeo - Llamar Sonido de notificación Mensajes que contienen mi nombre público Mensajes que contienen mi nombre de usuario Mostrar marcas temporales en formato de 12 horas - Análisis de Estadísticas - Necesitas permiso para gestionar los componentes en esta sala La creación del componente falló Crear llamadas de conferencia con jitsi ¿Seguro que quieres eliminar el widget de esta sala\? - No es posible crear el componente. El envío de la solicitud falló. @@ -737,60 +630,43 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.La sala %s no está visible. Añadir aplicaciones de Matrix Utilizar cámara nativa - Añadiste una nueva sesión \'%s\', que está solicitando claves de cifrado. Tu sesión sin verificar \'%s\' está solicitando claves de cifrado. Iniciar verificación Compartir sin verificar Ignorar solicitud - ¡Advertencia! Las llamadas de conferencia están en desarrollo y pueden no ser confiables. - Error de comando Comando no reconocido: %s - Desactivado Ruidoso - Mensaje cifrado - Detalles de comunidad - Cargando… - Salir Comunidades - Filtrar comunidades - Invitar Comunidades No hay grupos - ¿Seguro que quieres iniciar una nueva conversación con %s? ¿Seguro que quieres iniciar una llamada de voz? ¿Seguro que quieres iniciar una llamada de vídeo? - Lista de Grupos - El usuario baneado lo echará de esta sala y evitará que se unan nuevamente. - Todos los mensajes (ruidoso) Todos los mensajes Solo menciones Silenciar - Añadir un Atajo a la Pantalla de Inicio - + Añadir a la Pantalla de Inicio Vistas previas de URL en línea Vibrar al mencionar un usuario - Insignia - Notificaciones Esta sala no está mostrando insignias para ninguna comunidad Nueva ID de comunidad (ej. +foo:matrix.org) @@ -798,48 +674,40 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.Olvidar sala Volver a unirse \'%s\' no es una ID de comunidad válida - - Crear Crear Comunidad Nombre de comunidad Ejemplo ID de Comunidad ejemplo - Inicio Personas Salas No hay usuarios - Salas Se unió Invitado Filtrar miembros del grupo Filtrar salas del grupo - Has sido expulsado de %1$s por %2$s Has sido vetado de %1$s por %2$s Motivo: %1$s El administrador de la comunidad no ha redactado una descripción larga para esta comunidad. - Agitar con rabia para reportar un error - Acciones Listar miembros Sincronizando… - 1 miembro + %d miembro %d miembros - 1 mensaje nuevo + %d mensaje nuevo %d mensajes nuevos - - 1 sala + %d sala %d salas @@ -851,13 +719,12 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.%d salas - 1 cambio de membresía + %d cambio de membresía %d cambios de membresía - Abrir título - 1 miembro activo + %d miembro activo %d miembros activos @@ -869,27 +736,21 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.%d mensajes notificados sin leer %1$s en %2$s - 1 componente activo %d componentes activos - Enviar una pegatina - Actualmente no tienes ningún paquete de pegatinas habilitado. \n \n¿Añadir algunos ahora\? - Desactivar Cuenta Avatar - Avatar de recibo Avatar de aviso Para continuar utilizando el servidor doméstico %1$s, debes revisar y aceptar los términos y condiciones. Revisar ahora - Esto hará que tu cuenta quede permanentemente inutilizable. No podrás iniciar sesión, y nadie podrá volver a registrar la misma ID de usuario. Esto hará que tu cuenta salga de todas las salas en las cuales participa, y eliminará los datos de tu cuenta de tu servidor de identidad. Esta acción es irreversible. Desactivar tu cuenta no hace que por defecto olvidemos los mensajes que has enviado. Si quieres que olvidemos tus mensajes, por favor marca la casilla a continuación. @@ -898,7 +759,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Por favor, olvida todos los mensajes enviados al desactivar mi cuenta (Advertencia: esto provocará que los usuarios futuros vean conversaciones incompletas) Para continuar, ingresa tu contraseña por favor: Desactivar Cuenta - Privacidad de notificaciones Normal Privacidad reducida @@ -908,51 +768,37 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu • El contenido del mensaje de la notificación está ubicado de forma segura directamente desde el servidor doméstico de Matrix • Las notificaciones contienen datos de mensajes y metadatos • Las notificaciones no mostrarán el contenido del mensaje - Desactivar cuenta Desactivar mi cuenta - Descargar Sí, ¡quiero ayudar! - Conceder permiso Enviar audio - Enviar pegatina Un parámetro no es válido. Ingresa tu contraseña por favor. - Enviar mensaje de voz - Elige otra opción - Falta un parámetro requerido. Solicitud enviada Conversar Por favor, inicia Element en otro dispositivo que pueda descifrar el mensaje para que pueda enviar las claves a esta sesión. - Licencias de terceros - Borrar continuar con… Lo sentimos, no se encontró ninguna aplicación externa para completar esta acción. - Volver a solicitar las claves de cifrado de tus otras sesiones. - Solicitud de clave enviada. - Privacidad de Notificaciones Element puede ejecutarse en segundo plano para gestionar tus notificaciones de forma segura y privada. Esto podría afectar la duración de la batería. Enviar datos de análisis de estadísticas Element recopila análisis de estadísticas anónimas para permitirnos mejorar la aplicación. Por favor, habilita los análisis de estadísticas para ayudarnos a mejorar Element. Escribe aquí… - Si es posible, por favor escribe la descripción en inglés. Enviar una respuesta cifrada… Enviar una respuesta (sin cifrar)… Actualmente no eres miembro de ninguna comunidad. - Utilizar la tecla Intro del teclado para enviar mensajes Muestra la acción Veta al usuario con la ID dada @@ -966,73 +812,58 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Cambia tu apodo público Activar/Desactivar markdown Para reparar la gestión de las Aplicaciones de Matrix - Vista previa de medios antes de enviar - Esta sala ha sido reemplazada y ya no está activa La conversación continúa aquí Esta sala es una continuación de otra conversación Haz clic aquí para ver mensajes más antiguos - Degrada al usuario con la ID dada Alertas de Sistema - - Debido a que faltan permisos, esta acción no es posible. + Debido a permisos insuficientes, esta acción no es posible. - 1s + $d %ds - 1m - %dm + %dmin + %dmins - 1h + %dh %dh - 1d + %dd %dd - %1$s ahora hace %1$s %2$s - "%1$s, " %1$s y %2$s %1$s %2$s - - 1 seleccionado + $d seleccionado %d seleccionados 1 miembro %d miembros - 1 sala %d salas Límite de Recursos Excedido Contacta al Administrador - contacta al administrador de tu servicio - Este servidor doméstico ha excedido uno de sus límites de recursos, por lo que algunos usuarios no podrán iniciar sesión. Este servidor doméstico ha excedido uno de sus límites de recursos. - Este servidor doméstico ha alcanzado su límite Mensual de Usuarios Activos, por lo que algunos usuarios no podrán iniciar sesión. Este servidor doméstico ha alcanzado su límite Mensual de Usuarios Activos. - Por favor, %s para aumentar este límite. Por favor, %s para continuar utilizando este servicio. - Tema de Status.im - Error - Versión %s Por favor, crea una frase de contraseña para cifrar las claves exportadas. Necesitarás ingresar la misma frase de contraseña para poder importar las claves. Crear frase de contraseña @@ -1040,27 +871,19 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Utiliza carga diferida para los miembros de la sala Aumenta el rendimiento cargando los miembros de la sala solo en la primera vista. Tu servidor doméstico aún no admite la carga diferida de los miembros de la sala. Prueba más tarde. - Disculpas, ocurrió un error - expandir colapsar - - llamar de cada manera + Llamar de todos modos Aceptar - Por favor revisa y acepta las reglas de este servidor doméstico: - Llamadas Usar el tono de llamada normal de Element para llamadas entrantes Tono para llamadas entrantes Elegir sonido de llamadas: - Llamada de video en proceso… - Expulsar Razón - Diagnóstico de fallas Diagnóstico de errores Iniciar pruebas @@ -1068,7 +891,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Iniciando servicio Copia de seguridad de la clave Usar copia de seguridad de la clave - La copia de seguridad de la clave no ha finalizado, por favor espere… No quiero mis mensajes cifrados Creando copia de seguridad de las claves… @@ -1076,43 +898,36 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu ¿Estás seguro\? Copia de seguridad Perderá el acceso a sus mensajes cifrados si cierra sesión sin hacer una copia de seguridad de sus claves. - Quedarse Saltar Hecho Cancelar Ignorar - Marcar como leído Iniciar sesión con single-sign-on Tu dispositivo usa una versión anticuada e insegura del protocolo de seguridad TLS. Por tu seguridad no puedes conectarte Ajustes avanzados de notificaciones Importancia de notificación por evento - Ajustes de sistema. Las notificaciones están activadas en los ajustes de sistema. Las notificaciones están desactivadas en los ajustes del sistema. \nPor favor comprueba los ajustes de sistema. Abrir ajustes - Ajustes de cuenta. Las notificaciones están activadas para tu cuenta. Las notificaciones están desactivadas para tu cuenta. \nPor favor comprueba los ajustes de cuenta. Activar - Ajustes de sesión. Las notificaciones están activadas para esta sesión. Las notificaciones no están habilitadas para esta sesión. \nPor favor comprueba los ajustes Element. Activar - Ajustes personalizados. Ten en cuenta que algunos mensajes son silenciosos (producen una notificación sin sonido). Algunas notificaciones están desactivadas en tus ajustes personalizados. Error al cargar reglas personalizadas, por favor prueba de nuevo. Comprueba ajustes - Prueba de servicios Google Play APK de servicios de Google Play esta disponible y actualizado. Al cerrar la sesión se perderán los mensajes encriptados @@ -1121,13 +936,11 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu El diagnóstico base se ha completado con éxito. Si aun no recibes notificaciones, por favor mándanos un informe de error. Una o más pruebas han fallado, por favor prueba las soluciones propuestas. Una o más pruebas han fallado, por favor mándanos un informe de error para que podamos investigar. - Copia de seguridad en progreso. Si cierras sesión ahora perderás el acceso a tus mensajes encriptados. La copia de seguridad debería estar activa ahora en todas tus sesiones para evitar la pérdida del acceso a tus mensajes encriptados. Element usa los servicios de Google Play para entregar mensajes Push pero no parece estar configurado correctamente: \n%1$s solucionar error con los Servicios de Google Play - Token Base Token FCM recuperada correctamente:\n%1$s Error al recuperar token FCM:\n%1$s @@ -1135,45 +948,36 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu [%1$s]\nEste error esta fuera del control de Element. Puede ocurrir por numerosas razones. Probablemente funcione si vuelve a intentarlo mas tarde. También puede comprobar si los Servicios de Google Play están limitados por los ajustes del sistema o si la hora del dispositivo es correcta o si puede pasar en ROM personalizada. [%1$s] \nEste error esta fuera del control de Element. No hay cuenta de googled registrada en este dispositivo. Por favor abre el gestor dde cuentas y añade una cuenta de Google. - añadir cuenta - + Añadir cuenta Token de registro Token FCM registrado correctamente en el Servidor. Error al registrar el token FCM en el Servidor \n%1$s - Servicio de notificaciones El servicio de notificaciones esta funcionando. El servicio de notificaciones no esta funcionando. \nIntente reiniciar la aplicación. Borrando copia de seguridad… Error al borrar la copia de seguridad (%s) - Borrar copia de seguridad nueva copia de seguridad Ese era yo nunca se pierden mensajes cifrados Configurar copia de seguridad de las claves de cifrado - Nunca pierdas mensajes cifrados Nuevos mensajes clave cifrados Gestionar Copia de Seguridad - Guardando copia de seguridad… - Versión Algoritmo Reinicio automático del servicio de notificaciones Empezar servicio - El servicio se ha apagado y reiniciado automáticamente. Error al reiniciar el servicio - Inicio automático El servicio funcionará cuando reinicie el dispositivo. El servicio no se iniciará al reiniciar el dispositivo, no recibirá notificaciones hasta que Element haya sido abierto al menos 1 vez. - activar Inicio automático - + Activar Inicio automático Comprobar restricciones en segundo plano Las restricciones de segundo plano están desactivadas para Element. Este debería funcionar con datos móviles (sin WIFI). \n%1$s @@ -1181,19 +985,15 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \nLa app estará completamente restringida mientras esté en segundo plano y esto podría afectar a las notificaciones. \n%1$s Desactivar restricciones - Optimización de la bateria A Element no le afecta la Optimización de la bateria. Si un usuario deja el dispositivo desenchufado e inmóvil durante cierto periodo de tiempo con la pantalla apagada, el dispositivo entrará en modo hibernación. Esto evita que las apps accedan a la red y postpone sus tareas, sincronizaciones y alarmas. ignorar optimización - Las apps no necesita conectarse al servidor doméstico en segundo plano, esto debería reducir el uso de la batería Configurar notificaciones de sonido Configurar notificaciones de llamada Configurar notificaciones silenciadas elegir Color de las luces LED, vibración. sonido… - - Administrar Claves de la criptografía Mostrar vistas previas de enlaces en el chat cuando el servidor doméstico soporte esta característica. Enviar notificaciones de escritura @@ -1208,31 +1008,24 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Incluye cambios en el avatar y en el nombre. Enviar mensaje con intro La tecla Intro enviará el mensaje en vez de añadir un salto de línea - Conexión en segundo plano Element necesita mantener una leve conexión en segundo plano para poder ofrecer notificaciones de confianza. \nEn la siguiente pantalla se le pedirá permisos para que Element siempre funcione en segundo plano, por favor acepte. Conceder permiso - El modo de guardado de datos aplica un filtro específico para que las actualizaciones de presencia y las notificaciones de escritura sean eliminadas. - Ha ocurrido un error mientras se verificaba tu dirección de correo electrónico. - Contraseña Actualizar contraseña La contraseña no es válida La contraseña no es correcta - Ha ocurrido un error tratando de verificar su número de teléfono. Información adicional: %s - Media Compresión predeterminada Seleccionar Seleccionar Recuperación de mensajes cifrados Gestionar copia de seguridad clave - %1$s: 1 mensaje %1$s: %2$d mensajes @@ -1241,49 +1034,39 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu %d notificación %d notificaciones - Nuevo evento Sala Nuevos mensajes Nueva invitación Yo ** Error al enviar - por favor abra la sala - "Lo sentimos, las llamadas grupales con Jitsi no se pueden mantener en dispositivos antiguos (dispositivos con Android inferior a 5.0)" - Iniciar la cámara del sistema en lugar de la pantalla de cámara personalizada. Esta opción requiere una aplicación de terceros para grabar los mensajes. - El comando \"%s\" necesita mas parámetros o algunos parámetros son incorrectos. Markdown activado. Markdown desactivado. - Silencioso Por favor introduzca un nombre de usuario. Mostrar el área de información Siempre Para mensajes y errores Solo para errores - %1$s: %1$s: %2$s +%d %d+ No se ha encontrado ningún APK válido de Servicios de Google Play. Las notificaciones podrían no funcionar correctamente. - Por favor introduzca una contraseña La contraseña que has introducido es muy débil - Por favor borra la contraseña si quieres que Element genere una clave de recuperación. No hay ninguna sesión de Matrix disponible - Nunca se pierden los mensajes cifrados Los mensajes en salas cifradas están asegurados con cifrado de extremo a extremo. Solo los integrantes de la sala y tu podéis leer estos mensajes. \n \nAsegúrate de guardar bien tus claves para evitar perderlas. (Avanzado) Exportar claves manualmente - Asegura tu copia de seguridad con una contraseña. Almacenaremos una copia cifrada de tus claves en tu servidor. Protege tu copia de seguridad con una contraseña para mantenerla segura. \n @@ -1305,7 +1088,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu La clave de recuperación ha sido guardada en \'%s\'. \n \nAtención: Este archivo podría borrarse si la aplicación es desinstalada. - Por favor, haga una copia Compartir clave de recuperación con… Generando clave de recuperación usando una contraseña, este proceso puede tardar varios segundos. @@ -1313,24 +1095,17 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Error inesperado Copia de seguridad iniciada Tus claves cifradas están siendo guardadas en segundo plano en tu servidor. La copia de seguridad inicial podría tardar varios minutos. - - Estás seguro\? Podrías perder el acceso a tus mensajes si te desconectas o pierdes este dispositivo. - Utiliza tu clave de recuperación para desbloquear tu historial de mensajes cifrados Utiliza tu clave de recuperación No sabes tu clave de recuperación\? puedes %s. - Utiliza tu clave de recuperación para desbloquear tu historial de mensajes cifrados Introduzca la clave de recuperación - Mensaje de recuperación - Has perdido tu clave de recuperación\? Puedes crear una nueva en ajustes. La copia de seguridad no se ha podido descifrar con esta contraseña: por favor verificar que has introducido la contraseña de recuperación correcta. Error de red: por favor comprueba tu conexión y vuelve a intentarlo. - Restaurando copia de seguridad: Creando clave de recuperación… Descargando claves… @@ -1338,7 +1113,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Desbloquear historial Por favor introduce una clave de recuperación La copia de seguridad no se ha podido descifrar con esta contraseña: por favor verificar que has introducido la contraseña de recuperación correcta. - Copia de seguridad restaurada %s ! Copia de seguridad restaurada con la clave %d. @@ -1348,36 +1122,27 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Se ha añadido %d como clave a esta sesión. Se han añadido %d como claves a esta sesión. - Error al recuperar la ultima versión de las claves (%s). La sesión crypto no esta activada - - Restaurada desde copia de seguridad Borrar copia de seguridad - La copia de seguridad ha sido correctamente activada para esta sesión. La copia de seguridad ha sido correctamente desactivada para esta sesión. Tus claves no están siendo guardadas en esta sesión. - La copia de seguridad tiene una firma de una sesión desconocida con el ID %s. La copia de seguridad tiene una firma valida de esta sesión. La copia de seguridad tiene una firma válida para la sesión verificada %s. Usar copia de seguridad de la clave - Todas las claves guardadas Cargando %d de la clave… Cargando %d de las claves… - Firma - autocompletar opciones del servidor Element ha detectado una configuración personalizada del servidor para el dominio de su ID de usuario \"%1$s\": \n%2$s Configuración de uso - Origen predeterminado de medios Configurar copia de seguridad de las claves de cifrado Obteniendo una versión de copia de seguridad… @@ -1385,18 +1150,14 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu La copia de seguridad tiene una firma inválida de la sesión verificada %s La copia de seguridad tiene una firma inválida de la sesión no verificada %s Error al conseguir información de confianza para la copia de seguridad (%s). - Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora. Desea borrar sus claves cifradas guardadas del servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados. - Una nueva copia de seguridad de mensajes ha sido detectada. \n \nSi no ha establecido un nuevo método de recuperación, alguien podría estar intentando acceder a su cuenta. Cambie su contraseña y establezca un nuevo método de recuperación inmediatamente en ajustes. Respuesta inválida del descubrimiento del servidor doméstico Reproducir sonido de cámara - Verificar sesión - ip desconocida Una nueva sesión solicita claves de cifrado. \nSesión: %1$s @@ -1406,53 +1167,42 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \nSesión: %1$s \nVisto por última vez: %2$s \nSi no has iniciado sesión en otro dispositivo ignora esta solicitud. - Verificar Compartir Petición de compartición de clave Ignorar - Ya existe una copia de respaldo en tu servidor Parece que ya habías configurado una copia de seguridad para las claves en otra sesión. ¿Quieres reemplazarla por la nueva que has creado\? Reemplazar Parar - Comprobando copias de respaldo Tu sesión ha terminado por credenciales caducadas o inválidas. - Verificar comparando un texto corto. Para más seguridad, te recomendamos que hagas esto en persona o por otros medios confiables. Empezar verificación Solicitud de verificación Verifica esta sesión para marcarla como confiable. Confiar en sesiones de otros te da aún más tranquilidad cuando usas cifrado de mensajes de punto a punto. Verificar esta sesión la marcará como confiable, y también marcará como confiable tu sesión para la contraparte. - Verifica esta sesión confirmando los emojis que aparecen en la pantalla de la contraparte Verifica esta sesión confirmando que los siguietes números aparecen en la pantalla de la contraparte - Se ha recibido una solicitud de verificación. Ver solicitud Esperando confirmación de la contraparte… - ¡Verificado! Has verificado correctamente esta sesión. - Los mensajes con este usuario están cifrados punto a punto y no son legible por terceros. + Los mensajes con este usuario están cifrados punto a punto y no son legibles por terceros. Ok - ¿No aparece nada\? No todas las aplicaciones cliente soportan verificación interactiva. Usa la verificación clásica. Usar verificación clásica. - Verificación de clave Solicitud cancelada La contraparte canceló la verificación. \n%s La verificación ha sido cancelada. \nRazón: %s - Verificación de sesión interactiva Solicitud de verificación %s quiere verificar tu sesión - El usuario canceló la verificación La verificación ha superado el límite de espera La sesión recibió un mensaje inesperado @@ -1460,35 +1210,27 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Error en clave Error en usuario Error desconocido - - Editar Responder - Reintentar Unirse a una sala y empezar a usar la app. Alguien te envió una invitación Invitado por %s - No tienes más mensajes sin leer ¡Bienvenido! Conversaciones Tus conversaciones directas (1 a 1) se mostrarán aquí Salas Tus salas se mostrarán aquí - Reacciones De acuerdo Me gusta Añadir reacción Ver reacciones Reacciones - Evento borrado por el usuario Evento moderado por el administrador de la sala Última edición por %1$s on %2$s - - Evento con error, no se puede mostrar Crear sala No hay red, por favor comprueba tu conexión a internet. @@ -1496,13 +1238,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Cambiar red Espere por favor… Todas la comunidades - Esta sala no se puede previsualizar La previsualización de salas públicas no es posible todavía con Element - Salas Mensajes directos - Nueva sala CREAR Nombre de la sala @@ -1510,10 +1249,8 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Cualquiera puede unirse a esta sala Directorio de salas Publicar esta sala en el directorio de salas - Error obteniendo información de confiabilidad Error obteniendo claves para copias de respaldo - !Estás al día! Preferencias Seguridad & Privacidad @@ -1522,31 +1259,23 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Por favor escriba su sugerencia a continuación. Describa su sugerencia aquí Latn - Ninguno Revocar Desconectar Revisar Declinar - No se ha configurado un servidor de identidad. - La llamada ha fallado por un servidor mal configurado Intente usar %s No volver a preguntar - Para hacer esto, vaya a las opciones y añada un servidor de identidad. Confirme su contraseña Eso no se puede hacer en Element para móvil Se necesita autenticación - - Optimizado para batería Optimizado para operar en tiempo real Sin sincronización en segundo plano No se han podido actualizar las opciones. - - Integraciones Descubrimiento Gestione sus preferencias de descubrimiento. @@ -1562,7 +1291,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu El SAS no coincidió Pon un número de teléfono para que las personas que conoces te puedan encontrar. Se usará %s como asistencia cuando el servidor doméstico no la ofrezca (su dirección IP se compartirá durante una llamada) - Modo sincronización en segundo plano (Experimental) + Modo Sincronización en segundo plano Element se sincronizará en segundo plano de manera que se preserven los recursos del dispositivo (batería). \nDependiendo del estado de los recursos del dispositivo, la sincronización puede ser aplazada por el sistema operativo. Element se sincronizará en segundo plano periódicamente en un momento preciso (configurable). @@ -1575,7 +1304,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \nLos Gestores de Integración reciben los datos de configuración y pueden modificar los widgets, enviar invitaciones a salas y establecer niveles de poder en su nombre. Permitir integraciones Adiministrador de integraciones - Nombre público (visible por las personas con quien te comuniques) Un nombre de sesión público es visible por las personas con quién te comunicas Widget @@ -1588,110 +1316,77 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Recargar widget Abrir en el navegador Revocar acceso para mi - Tu nombre visible La URL de tu avatar Tu ID de usuaria Tu tema ID de Widget ID de Sala - - Este widget quiere usar los siguientes recursos: Permitir Bloquear todos Usar la cámara Usar el micrófono Leer medios protegidos por DRM - No se ha configurado ningún administrador de integraciones. Para continuar es necesario que aceptes los Términos de este servicio. - La sesión no sabe nada de esa transacción La sesión no puede acordar el acuerdo de llaves, hash, MAC o método SAS El compromiso hash no ha coincidido No estás usando ningún Servidor de Identidad No hay ningún Servidor de Identidad configurado, esto es requerido para restablecer tu contraseña. - Parece que estás intentando conectarte a otro servidor doméstico. ¿Quieres cerrar sesión\? - Importar llaves E2E des del fichero \"%1$s\". - Versión del SDK de Matrix Otros avisos de terceros ¡Ya estas viendo esta sala! - Reacciones rápidas - Cuenta Experto Reglas Push No hay reglas push definidas No hay salidas push registradas - app_id: push_key: app_display_name: Url: Formato: - Ayuda y Acerca de - - Registrar token - Gracias, la sugerencia ha sido enviada correctamente El envio de la sugerencia ha fallado (%s) - Mostrar eventos ocultos en la línea de tiempo - Mensajes Directos - Esperando… Cifrando la miniatura… Enviando miniatura (%1$s / %2$s) Cifrando el archivo… Enviando el archivo (%1$s / %2$s) - Descargando archivo %1$s… ¡El archivo %1$s ha sido descargado! - (editado) - - Modificación de mensajes No se han encontrado modificaciones - Filtrar conversaciones… ¿No encuentras lo que buscas\? Crear una nueva sala Enviar un nuevo mensaje directo Ver el directorio de la sala - Nombre o ID (#ejemplo:servidor.org) - Habilitar \"desplazar para contestar\" en la línea de tiempo - Enlace copiado al portapapeles - Agregar por ID de matrix Creando sala… No se ha encontrado ningún resultado, utiliza \"Agregar usando ID de matrix\" para buscar en el servidor. Empieza a escribir para ver resultados Filtrar por usuario o ID… - Entrando en la sala… - Ver historial de modificaciones - Términos de Servicio Revisar Términos Ser descubierta por otros Utiliza Bots, puentes, widgets y packs de stickers - Leer en - - Servidor de identidad Desconectar servidor de identidad Configurar servidor de identidad @@ -1705,27 +1400,19 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Teléfonos para ser descubierto Te hemos enviado un correo de confirmación a %s, comprueba tu correo y haz click en el enlace de confirmación Pendiente - Entra en un nuevo servidor de identidad No se ha podido conectar al servidor de identidad Porfavor entra la url del servidor de identidad El servidor de identidad no tiene términos de servicio El servidor de identidad que has escojido no tiene términos de servicio. Solo continúa si confias en el propietario del servicio Un mensaje de texto ha sido enviado a %s. Porfavor escribe el código de verificación que contiene. - Actualmente estás compartiendo direcciones de correo electrónico o números de teléfono en el servidor de identidad %1$s. Necesitarás reconectarte a %2$s para dejar de compartirlos. Acepte los términos de servicio del servidor de identidad (%s) para permitir que sea descubierto por correo electrónico o número de teléfono. - Habilitar registros extensos. Los logs extensivos ayudarán a los desarrolladores proporcionando más información cuando envíes un \"RageShake\" (Sacudir el dispositivo). Incluso cuando esto está habilitado, la aplicación no registra el contenido de los mensajes ni ningún otro dato privado. - - Por favor, vuelva a intentarlo una vez que haya aceptado los términos y condiciones de su servidor. - Parece que el servidor está tardando demasiado en responder, esto puede ser causado por una mala conectividad o un error con el servidor. Por favor, inténtelo de nuevo en un rato. - Enviar el archivo adjunto - Abrir el cajón de navegación Abrir el menú de creación de sala Cerrar el menú de creación de sala… @@ -1735,17 +1422,14 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Mostrar contraseña Esconder contraseña Saltar al final - Leído por %1$s, %2$s y %3$s Leído por %1$s y %2$s Leído por %s - Leído por 1 usuario - Leído por %d usuarias + Leído por %d usuario + Leído por %d usuarios - El archivo \'%1$s\' (%2$s) es demasiado grande para ser subido. El límite es %3$s. - Ha ocurrido un error recuperando el archivo adjunto. Archivo Contacto @@ -1754,7 +1438,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Galería Pegatina No se han podido compartir los datos - Es spam Es inapropiado Reporte personalizado… @@ -1762,28 +1445,23 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Razón por la que se ha reportado el contenido REPORTAR BLOQUEAR USUARIO - Contenido reportado - El contenido ha sido reportado + El contenido ha sido reportado. \n -\nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes +\nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes. Reportar como spam Este contenido ha sido reportado como spam. \n -\nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes +\nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes. Reportado como inapropiado Este contenido fue reportado como inapropiado. \n -\nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes - +\nSi no quieres ver más contenido de este usuario, puedes bloquearlo para ocultar sus mensajes. Element necesita permiso para guardar tus claves E2E en la memória del dispositivo. \n \nPorfavor permite el acceso en el siguiente pop-up para poder exportar tus claves manualmente. - No hay conexión de red - Ignorar usuario - Todos los mensajes (sonido) Todos los mensajes Solo menciones @@ -1794,29 +1472,22 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Envía el mensaje como spoiler Revelación Escribe las palabras clave para encontrar una reacción. - No hay usuarios ignorados - Mantén pulsada una sala para ver más opciones - - %1$s ha hecho la sala pública para cualquier persona con el link. %1$s: Ahora la sala solo es accesible por invitación. Mensajes no leídos - - Es tu conversación. Me pertenece. + Es tu conversación. Hazla tuya. Envía mensajes a personas o grupos Mantén las conversaciones privadas con encriptación Extiende y personaliza tu experiencia Empieza - Selecciona un servidor Como el correo electrónico, las cuentas tienen un hogar, aunque se puede hablar con cualquiera Alojamiento de pago para organizaciones Saber más Otro Ajustes avanzados y de personalización - Continuar Conectarse a %1$s Conectarse a Element Matrix Services @@ -1828,7 +1499,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Alojamiento de pago para organizaciones Introduzca la dirección de Modular Element o servidor que quieres usar Introduzca la dirección del servidor Element al que quieres conectarte - Se produjo un error al cargar la página: %1$s (%2$d) "La aplicación no es capaz de iniciar sesión en este servidor. Este solo soporta el acceso mediante: %1$s. \n @@ -1837,25 +1507,18 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu La aplicación no fue capaz de crear una cuenta en este servidor. \n \n¿Quieres registrarte usando un cliente web\? - La dirección de coreo electrónico no está asociada a ninguna cuenta. - Reiniciar contraseña en %1$s ¡Las claves ya están al día! - Reproducir Pausar Descartar - - Copiar Correcto - Notificaciones Element Fallo la Llamada Fallo al intentar establecer conexion. \nTURN Server fallo. Por favor, contacte con el administrador de su Servidor y notifique el fallo. - Seleccionar Dispositivo de Sonido Telefono Altavoz @@ -1866,13 +1529,11 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Tracera Apagar HD Activar HD - Error SSl: la identidad del par no a sido verificada. Error SSL. Permitir servidor de asistencia de llamadas Llamada activa (%s) Regresar a la llamada - Cancelar invitación Ignorar Usuario Cancelar Invitacion @@ -1881,7 +1542,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Verifica este enlace Este link %1$s loredirecciona a otro sitio %2$s. . \nEsta seguro de continuar\? - Adicionar miembros INVITAR Invitando usuarios… @@ -1893,11 +1553,9 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Invitaciones enviadas a %$s y a %2$d más No se pudo invitar el usuario. Por favor, intente nuevamente. - Idioma actual Otros idiomas disponibles Cargando lenguajes disponibles… - Leer los terminos de %s Desconectarse del servidor de Identidad %s\? Servidor de identidad desactualizado. Element solo soporta API V2. @@ -1906,20 +1564,17 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Riot ahora es Element! Entendido Aprender Mas - Element - - Buscar en mis contactos Rechazar invitación Confirma PIN para desabilitarlo No posee permisos para iniciar una conferencia en esta sala Conferencia en progreso! - Iniciar Video Conferencia - Iniciar Audio Conferencia - No puedes hacer una llamada contigo mismo - No puedes hacer una llamada contigo mismo, espera a que los participantes acepten la invitación - Fallo al adicionar Widget + Iniciar Videoconferencia + Iniciar Audioconferencia + No puedes hacer llamarte a tí mismo + No puedes hacer llamarte a tí mismo, espera a que los participantes acepten la invitación + Fallo al añadir Widget Fallo al eliminar Widget Confirmar llamada Pedir confirmacion antes de iniciar una llamda @@ -1928,14 +1583,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Rason de baneo Desbanear usuario Desbanear usuario y permitir entrar a la sala nuevamente. - nombre_session: Adicionar pestaña dedicada para notificaciones no leidas en la pantalla principal. - Descripcion muy corta - Sincronización inicial… - Mostrar todas mis sessiones Opciones Avanzadas Modo Desarrollador @@ -1946,33 +1597,25 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Ajustes Sesión Actual Otras Sesiones - Mostrando solo el primer resultado, agregue mas letras… - Fallar rápido (Test) Element puede fallar con más frecuencia cuando ocurre un error inesperado - Antepone ¯\\_(ツ)_/¯ a un mensaje de texto sin formato - Habilitar encriptacion Una vez habilitada, el cifrado no se puede deshabilitar. - Su dominio de correo electrónico no está autorizado para registrarse en este servidor - Inicio de sesión no confiable Coinciden No coinciden Verifique a este usuario confirmando que el siguiente emoji único aparece en su pantalla, en el mismo orden. Para mayor seguridad, use otro medio de comunicación confiable o hágalo en persona. Busque el escudo verde para asegurarse de que se confía en un usuario. Confíe en todos los usuarios de una sala para asegurarse de que la sala sea segura. - No seguro Video. Imagen. Audio Archivo Sticker - Esperando… %s cancelada Cancelado por usted @@ -1982,21 +1625,15 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Solicitud de verificación Verifica esta Sesion Verificar manualmente - Usted - Escanee el código con el dispositivo del otro usuario para verificarse mutuamente de forma segura Escanear código Error al escanear Si no estás en persona, compara los emojis - Verificar comparando emojis - Verificar por emojis Si no puede escanear el código anterior, verifique comparando una selección breve y única de emoji. - Imagen de código QR - Verificar %s Verificado %s Esperando por %s… @@ -2014,109 +1651,78 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Archivos, Medias y Documentos Abandonar Sala Saliendo de la sala… - Administradores Moderadores Nivel Personalizado Invitados Usuarios - Administrador en %1$s Moderador en %1$s Nivel Personalizado en %1$s Nivel Personalizado (%1$d) en %2$s - Saltar para leer el recibo - Element no maneja eventos de tipo \'%1$s\' Element no maneja el mensaje de tipo \'%1$s\' Element encontró un problema al representar el contenido del evento con el ID \'%1$s\' - Dejar de ignorar - Salas recientes Otras salas - Envía el mensaje dado en colores Línea de tiempo - Editor de mensage - Encriptar (end-to-end) Una vez habilitado, el cifrado no se puede deshabilitar. - Encriptar \? Habilitar el cifrado - Firma cruzada Firma cruzada no habilitada - Sesiones Activas Mostrar todas las Sesiones Administrar Sesiones Cerrar Sesión - Verificar este inicio de sesión Otros usuarios pueden no confiar en la sesion Completar Seguridad - Verificar Verificada Precaucion - Error al obtener sesiones Sesiones Confirmado No es confiable - Inicializar Firmas Cruzadas Restablecer claves - Codigo QR - Correcto No - Sin conexión Modo Avión Activado - Herramientas de desarrollo Datos de cuenta Seleccionar Opcion Nuevo inicio de sesión - Advertencia: Remover… Razón Razón para redactar - Element Android - Refrescar - Nuevo inicio de sesión detectado . ¿Fue usted\? Toca para revisar y verificar Este no era yo Su cuenta puede estar comprometida - Verificación cancelada - Frase de contraseña de recuperación Clave de mensaje Contraseña de la cuenta - ¡Listo! Cifrado habilitado Sala creada y configurada por usted. - Esperando por %s… - Ajuste de Notificaciones Mensaje… - Introduza su %s para continuar Usar archivo - No se pudo guardar el archivo multimedia Verificar Sesión Confirmar PIN @@ -2128,27 +1734,20 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Numeros telefonicos Correos y numeros telefonicos Administre el correo y numero telefonico de su cuenta - Mostrar mensages eliminados Indicar marca de mensaje eliminado ARCHIVOS No se han subido archivos a la sala - Establecer notificaciones por eventos - Establecer una nueva contraseña… - Las reuniones utilizan políticas de seguridad y permisos de Jitsi. Todas las personas que se encuentren actualmente en la sala verán una invitación para unirse mientras se lleva a cabo la reunión. Aceptar Declinar Colgar - Este número de teléfono ya está definido. ¿Degradarte\? No podrá deshacer este cambio ya que se está degradando, si es el último usuario privilegiado en la sala, será imposible recuperar los privilegios. Degradar - - Si ignora a este usuario, se eliminarán sus mensajes de las salas que comparte. \n \n Puede revertir esta acción en cualquier momento en la configuración general. @@ -2164,7 +1763,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu No se ha agregado ningún correo electrónico a su cuenta ¿Elimina %s\? Asegúrese de haber hecho clic en el enlace del correo electrónico que le enviamos. - Copia de seguridad segura Gestionar Configurar copia de seguridad segura @@ -2173,47 +1771,34 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Protéjase contra la pérdida de acceso a los mensajes y datos cifrados haciendo una copia de seguridad de las claves de cifrado en su servidor. Genere una nueva llave de seguridad o establezca una nueva frase de seguridad para su copia de seguridad existente. Esto reemplazará su clave o frase actual. - Las integraciones están deshabilitadas Habilite \'Permitir integraciones\' en Configuración para hacer esto. - %d usuario prohibido %d usuarios prohibidos - Claves exportadas correctamente - %1$d/%2$d clave importada con éxito. %1$d/%2$d claves importadas con éxito. - %1$s: %2$s %1$s: %2$s %3$s - VER Widgets activos - - Gestionar integraciones Sin widgets activos La clave de recuperación se ha guardada. - Copia de seguridad segura Protéjase contra la pérdida de acceso a mensajes y datos cifrados - Configurar copia de seguridad segura - Mensaje borrado Se ha creado la sala, pero algunas invitaciones no se han enviado por el siguiente motivo: \n \n%s - Le enviamos un correo electrónico de confirmación %s, primero revise su correo electrónico y haga clic en el enlace de confirmación Código El código de verificación no es correcto. - %1$s, %2$s y %3$d otra lectura %1$s, %2$s y %3$d otras lecturas @@ -2226,61 +1811,49 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu No hiciste cambios Hiciste que la sala fuera pública para quien conozca el enlace. Hiciste la sala solo por invitación. - Únase a millones gratis en el servidor público más grande + Únase gratis a millones en el servidor público más grande Continuar con SSO - Dirección de servicios de Element Matrix Ingrese la dirección del servidor que desea utilizar - Se enviará un correo electrónico de verificación a su bandeja de entrada para confirmar la configuración de su nueva contraseña. Próximo Email Nueva contraseña - ¡Advertencia! Cambiar su contraseña restablecerá cualquier clave de cifrado de extremo a extremo en todas sus sesiones, haciendo ilegible el historial de chat cifrado. Configure Key Backup o exporte las llaves de su sala desde otra sesión antes de restablecer su contraseña. Seguir - Este correo electrónico no está vinculado a ninguna cuenta - Revisa tu correo Se envió un correo electrónico de verificación a %1$s. Toque el enlace para confirmar su nueva contraseña. Una vez que haya seguido el enlace que contiene, haga clic a continuación. He verificado mi dirección de correo electrónico - ¡Éxito! Tu contraseña ha sido restablecida. Ha cerrado sesión en todas las sesiones y ya no recibirá notificaciones automáticas. Para volver a habilitar las notificaciones, inicie sesión nuevamente en cada dispositivo. Volver a Iniciar sesión - Advertencia u contraseña aún no ha cambiado. \n \n¿Detener el proceso de cambio de contraseña\? - Establecer dirección de correo electrónico Configure un correo electrónico para recuperar su cuenta. Más tarde, opcionalmente, puede permitir que las personas que conoce lo descubran mediante su correo electrónico. Correo electrónico Email (opcional) Próximo - Establecer número de teléfono Configure un número de teléfono para permitir que las personas que conoce lo descubran opcionalmente. Utilice el formato internacional. Número de teléfono Numero de teléfono (opcional) Próximo - Confirmar número de teléfono Acabamos de mandar un codigo a %1$s. Ingréselo a continuación para verificar que es usted. Introduzca el código Enviar de nuevo Próximo - Utilice el formato internacional (el número de teléfono debe comenzar con \'+\') Los números de teléfono internacionales deben comenzar con \'+\' El número de teléfono parece no válido. Compruébelo por favor - Inscribirse a %1$s Nombre de usuario o correo electrónico Nombre de usuario @@ -2291,25 +1864,21 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Tu cuenta aún no está creada. \n \n ¿Detener el proceso de registro\? - Seleccione matrix.org Seleccionar servicios de matriz de elementos Seleccione un servidor doméstico personalizado Realiza el desafío de captcha Acepta los términos para continuar - Por favor revise su correo electrónico Acabamos de enviar un correo electrónico a %1$s. \nHaga clic en el enlace que contiene para continuar con la creación de la cuenta. El código introducido no es correcto. Por favor, compruebe. Servidor doméstico obsoleto Este servidor doméstico está ejecutando una versión demasiado antigua para conectarse. Pídale al administrador de su servidor doméstico que actualice. - Se han enviado demasiadas solicitudes. Puedes volver a intentarlo en %1$d segundo… Se han enviado demasiadas solicitudes. Puedes volver a intentarlo en %1$d segundos… - Alternativamente, si ya tiene una cuenta y conoce su identificador Matrix y su contraseña, puede usar este método: Iniciar sesión con Matrix ID Iniciar sesión con Matrix ID @@ -2318,9 +1887,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Si no conoce su contraseña, vuelva a restablecerla. "Este no es un identificador de usuario válido. Formato esperado: \'@user:homeserver.org\'" No se pudo encontrar un servidor de inicio válido. Por favor verifique su identificador - Visto por - Estás desconectado Puede deberse a varias razones: \n @@ -2330,7 +1897,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \n \n• El administrador de su servidor ha invalidado su acceso por motivos de seguridad. Iniciar sesión de nuevo - Estás desconectado Registrarse "El administrador de su servidor doméstico (%1$s) ha cerrado la sesión de su cuenta %2$s (%3$s)." @@ -2342,14 +1908,12 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \n \nBórrelo si terminó de usar este dispositivo o si desea iniciar sesión en otra cuenta. Borrar todos los datos - Borrar datos ¿Borrar todos los datos almacenados actualmente en este dispositivo\? \nVuelva a iniciar sesión para acceder a los datos y mensajes de su cuenta. Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de cifrado. Borrar datos La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no es compatible con Element. Primero borre los datos, luego inicie sesión nuevamente con otra cuenta. - Su enlace matrix.to estaba mal formado El modo desarrollador activa funciones ocultas y también puede hacer que la aplicación sea menos estable. ¡Solo para desarrolladores! Uno de los siguientes puede verse comprometido: @@ -2357,7 +1921,6 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \n- El servidor doméstico al que está conectado el usuario que estás verificando \n- La suya o la conexión a Internet de otros usuarios \n- El suyo o el dispositivo de otros usuarios - Para mayor seguridad, verifique %s verificando un código único en ambos dispositivos. \n \nPara máxima seguridad, hágalo en persona. @@ -2366,18 +1929,14 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \nSus mensajes están protegidos con candados y solo usted y el destinatario tienen las claves únicas para desbloquearlos. Esta sesión no puede compartir esta verificación con sus otras sesiones. \nLa verificación se guardará localmente y se compartirá en una versión futura de la aplicación. - Envía el emote dado coloreado como un arcoíris - Una vez habilitado, el cifrado de una sala no se puede deshabilitar. Los mensajes enviados en una sala cifrada no pueden ser vistos por el servidor, solo por los participantes de la sala. Habilitar el cifrado puede evitar que muchos bots y puentes funcionen correctamente. Para estar seguro, verifique %s comprobando un código de un solo uso. Para estar seguro, hágalo en persona o use otra forma de comunicarse. - Compare los emoji únicos, asegurándose de que aparezcan en el mismo orden. Compare el código con el que se muestra en la pantalla del otro usuario. Los mensajes con este usuario están encriptados de extremo a extremo y no pueden ser leídos por terceros. Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes cifrados y otros usuarios lo verán como de confianza. - La firma cruzada está habilitada \n Claves privadas en el dispositivo. La firma cruzada está habilitada @@ -2387,23 +1946,16 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \nLas claves no son de confianza El administrador de su servidor ha desactivado el cifrado de extremo a extremo de forma predeterminada en salas privadas y mensajes directos. No hay información criptográfica disponible - Esta sesión es confiable para mensajería segura porque usted la verificó: Verifique esta sesión para marcarla como confiable y otorgarle acceso a mensajes encriptados. Si no inició sesión en esta sesión, su cuenta puede verse comprometida: - %d sesión activa %d sesiones activas - Utilice una sesión existente para verificar esta, otorgándole acceso a los mensajes cifrados. - - "Esta sesión es confiable para mensajería segura porque %1$s (%2$s) la verificó:" %1$s (%2$s) iniciado sesión con una nueva sesión: Hasta que este usuario confíe en esta sesión, los mensajes enviados hacia y desde ella se etiquetan con advertencias. Alternativamente, puede verificarlo manualmente. - - ¡Casi ahí! ¿Es %s muestra el mismo escudo\? %d voto @@ -2416,31 +1968,24 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Crea una encuesta simple Use una contraseña o clave de recuperación Si no puede acceder a una sesión existente - No puedo encontrar secretos almacenados Ingrese la contraseña de almacenamiento secreta Solo debe acceder al almacenamiento secreto desde un dispositivo confiable - ¿Quieres enviar este adjunto a %1$s\? Enviar imagen con el tamaño original Envía imágenes con el tamaño original - Confirmar eliminación ¿Está seguro de que desea eliminar (eliminar) este evento\? Tenga en cuenta que si elimina el nombre de una sala o el cambio de tema, podría deshacer el cambio. Evento eliminado por el usuario, motivo: %1$s Evento moderado por el administrador de la sala, motivo: %1$s - Solicitudes clave - Desbloquear el historial de mensajes cifrados - Utilice esta sesión para verificar su nuevo, otorgándole acceso a mensajes encriptados. Si cancela, no podrá leer mensajes encriptados en este dispositivo y otros usuarios no confiarán en él Si cancela, no podrá leer mensajes encriptados en su nuevo dispositivo y otros usuarios no confiarán en él No verificarás %1$s (%2$s) si cancelas ahora. Comience de nuevo en su perfil de usuario. - Uno de los siguientes puede verse comprometido: \n \n- Tu contraseña @@ -2449,29 +1994,21 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \n- La conexión a Internet que está usando cualquiera de los dispositivos \n \nLe recomendamos que cambie su contraseña y clave de recuperación en Configuración de inmediato. - Verifique sus dispositivos desde Configuración. Establecer un %s Generar una clave de mensaje - Confirmar %s - "Ingrese su %s para continuar." - Proteja y desbloquee los mensajes cifrados y confíe en %s. Ingrese su %s nuevamente para confirmarlo. No use la contraseña de su cuenta. - Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. - Esto puede tardar varios segundos, tenga paciencia. Configurando la recuperación. Tu clave de recuperación Manténlo seguro Terminar - Utilice este %1$s como red de seguridad en caso de que olvide su %2$s. - Publicar claves de identidad creadas Generando clave segura a partir de frase de contraseña Definición de la clave predeterminada de SSSS @@ -2479,18 +2016,13 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Sincronización de la clave de usuario Sincronización de la clave de autofirma Configuración de copia de seguridad de claves - - Tus %2$s y %1$s ahora están configurados. \n \n¡Mantenlos a salvo! Los necesitará para desbloquear mensajes cifrados y proteger la información si pierde todas sus sesiones activas. - Imprímelo y guárdalo en un lugar seguro Guárdelo en una llave USB o unidad de respaldo Cópielo en su almacenamiento personal en la nube - No puedes hacer eso desde el móvil - Establecer una frase de contraseña de recuperación le permite proteger y desbloquear mensajes cifrados y de confianza. \n \nSi no desea establecer una contraseña de mensaje, genere una clave de mensaje. @@ -2498,35 +2030,28 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Si cancela ahora, puede perder mensajes y datos cifrados si pierde el acceso a sus inicios de sesión. \n \nTambién puede configurar la Copia de seguridad segura y administrar sus claves en Configuración. - Los mensajes de esta sala están cifrados de extremo a extremo. Obtenga más información y verifique a los usuarios en su perfil. Cifrado no habilitado El cifrado utilizado por esta sala no es compatible - %s creado y configurado la sala. ¡Casi ahí! ¿El otro dispositivo muestra el mismo escudo\? ¡Casi ahí! Esperando confirmación… No se pudieron importar las claves - Mensajes que contienen @room Mensajes cifrados en chats uno a uno Mensajes cifrados en chats grupales Cuando las salas son actualizadas Solucionar problemas Envía un mensaje como texto estándar, sin interpretarlo como Markdown - Nombre de usuario y / o contraseña incorrectos. La contraseña ingresada comienza o termina con espacios, verifíquela. Esta cuenta ha sido desactivada. - Mejora de encriptación disponible Habilitar la firma cruzada Verifíquese a usted mismo y a los demás para mantener sus chats seguros - Entrar %s Frase de contraseña de recuperación No es una clave de recuperación válida Por favor introduce una clave de recuperación - Comprobando la clave de respaldo Comprobando la clave de respaldo (%s) Obteniendo clave de curva @@ -2535,15 +2060,12 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Generando clave SSSS a partir de clave de recuperación Almacenar el secreto de la copia de seguridad de claves en SSSS %1$s (%2$s) - Ingrese su Frase de contraseña de respaldo de clave para continuar. use su clave de recuperación de Key Backup No conoces tu frase de contraseña de copia de seguridad clave, puedes %s. Clave de recuperación de copia de seguridad - Evitar capturas de pantalla de la aplicación Al habilitar esta configuración, se agrega FLAG_SECURE a todas las actividades. Reinicie la aplicación para que el cambio surta efecto. - Archivo multimedia agregado a la Galería No se pudo agregar el archivo multimedia a la Galería Utilice la última versión de Element en sus otros dispositivos, Element Web, Element Desktop, Element iOS, Element para Android u otro cliente Matrix con capacidad de firma cruzada @@ -2560,28 +2082,22 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Seleccione su clave de recuperación o introdúzcala manualmente escribiéndola o pegándola desde su portapapeles La copia de seguridad no se pudo descifrar con esta clave de recuperación: verifique que ingresó la clave de recuperación correcta. No se pudo acceder al almacenamiento seguro - Sin encriptar Cifrado por un dispositivo no verificado Revise dónde inició sesión Verifique todas sus sesiones para asegurarse de que su cuenta y sus mensajes estén seguros Verifique el nuevo inicio de sesión accediendo a su cuenta: %1$s - Verificar manualmente por texto Verificación interactiva por emoji Confirme su identidad verificando este inicio de sesión de una de sus otras sesiones, otorgándole acceso a los mensajes cifrados. Confirme su identidad verificando este inicio de sesión, otorgándole acceso a los mensajes cifrados. Marcar como de confianza - Lo sentimos, esta operación aún no es posible para las cuentas conectadas mediante el inicio de sesión único. - No pudimos crear tu DM. Marque los usuarios que desea invitar y vuelva a intentarlo. - Primero acepta los términos del servidor de identidad en la configuración. Para su privacidad, Element solo admite el envío de números de teléfono y correos electrónicos de usuario con hash. La asociación ha fallado. No hay asociación actual con este identificador. - Su servidor doméstico (%1$s) propone utilizar %2$s para su servidor de identidad Utilizar %1$s Alternativamente, puede ingresar cualquier otra URL del servidor de identidad @@ -2594,9 +2110,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Activar el sonido del micrófono Detén la cámara Enciende la cámara - Configurar copia de seguridad segura - Respaldo seguro Protéjase contra la pérdida de acceso a los mensajes y datos cifrados haciendo una copia de seguridad de las claves de cifrado en su servidor. Preparar @@ -2604,22 +2118,17 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Genere una clave de seguridad para almacenar en un lugar seguro, como un administrador de contraseñas o una caja fuerte. Utilice una frase de seguridad Ingrese una frase secreta que solo usted conozca y genere una clave de respaldo. - Guarde su llave de seguridad Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte. - Establecer una frase de seguridad Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. Frase de seguridad Ingrese su Frase de seguridad nuevamente para confirmarla. - Guarde su llave de seguridad Guarde su llave de seguridad en un lugar seguro, como un administrador de contraseñas o una caja fuerte. - Nombre de la Sala Tema Cambiaste la configuración de la sala con éxito - No puedes acceder a este mensaje Esperando este mensaje, esto puede tardar un poco No se puede descifrar @@ -2628,22 +2137,17 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu No puede acceder a este mensaje porque el remitente no confía en su sesión No puede acceder a este mensaje porque el remitente no envió las claves a propósito Esperando el historial de cifrado - ¡Nos complace anunciar que hemos cambiado de nombre! Tu aplicación está actualizada y accediste a tu cuenta. Guardar la clave de recuperación en - Agregar desde mi directorio telefónico Tu directorio telefónico está vacío Directorio telefónico Recuperando tus contactos… Tu libro de contactos está vacío Libro de contactos - ¿Revocar la invitación a %1$s\? - Prohibido por %1$s No se pudo anular la prohibición del usuario - Las notificaciones push están deshabilitadas Revise su configuración para habilitar las notificaciones push @@ -2653,9 +2157,41 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu ¡Advertencia! ¡Último intento restante antes de cerrar sesión! Demasiados errores, se ha desconectado Elija un PIN por seguridad - No se pudo validar el PIN, toque uno nuevo. + No se pudo validar el PIN, por favor introduzca uno nuevo. Introduce tu PIN ¿Olvidó su PIN\? No se puede abrir una sala en la que está prohibido. No puedo encontrar esta sala. Asegúrate de que exista. - + Los mensajes en esta sala están encriptados punto-a-punto. + Mensaje directo + Salir + Preferencias + Los mensajes aquí están encriptados de punto a punto. +\n +\nTus mensajes están asegurados con un candado. Solo tú y tú destinatario tenéis las llaves especiales para desencriptarlos. + Los mensajes aquí no están encriptados de punto a punto. + Botones de Bot + Encuesta + Remover de Baja prioridad + Añadir a Baja prioridad + Rotar y recortar + + $d segundo + $d segundos + + Por favor, haz click en la notificación. Si no la ves, por favor revisa las preferencias del sistema. + Mostrar notificación + ¡Estás viendo la notificación! ¡Haz click en mí! + Fallo al recibir Push. La solución puede ser el reinstalar la aplicación. + La aplicación está recibiendo PUSH + La aplicación está esperando al PUSH + Probar Push + Búsquedas en salas encriptadas todavía no están soportadas. + Filtrar usuarios excluidos + Enviar la historia de peticiones de claves compartidas + No hay más resultados + No posee permisos para iniciar una llamada + No posee permisos para iniciar una llamada en esta sala + No posee permisos para iniciar una conferencia + Resetear + \ No newline at end of file From d9ceb32e2f58634d67ccea040223c09b2985019f Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Fri, 13 Nov 2020 15:55:56 +0000 Subject: [PATCH 066/231] Translated using Weblate (Persian) Currently translated at 98.4% (1904 of 1933 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 | 153 +++++++++++----------- 1 file changed, 79 insertions(+), 74 deletions(-) diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 8188cbf2c6..ae3292bb2c 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -421,8 +421,8 @@ %d اتاق - %d اعلان - %d اعلان‌ها + %d آگاهی + %d آگاهی زمینه‌تان افزودن کاره‌های ماتریکس @@ -894,12 +894,12 @@ ممکن است کارساز در دسترس نبوده یا شلوغ باشد این‌جا بنویسید… - %d اعلان خوانده‌نشده - %d اعلان‌های خوانده‌نشده + %d پیام آگاهی نخوانده + %d پیام آگاهی نخوانده - %d اعلان خوانده‌نشده - %d اعلان‌های خوانده‌نشده + %d پیام آگاهی نخوانده + %d پیام آگاهی نخوانده %1$s: %2$d پیام @@ -1021,7 +1021,7 @@ ورود کلید بازیابی بازیابی پیام محاسبهٔ کلید بازیابی… - لطفاً‌یک کلید بازیابی وارد کنید + لطفاً یک کلید بازیابی وارد کنید من بودم هرگز پیام‌های رمزشده را از دست ندهید شروع با استفاده از پشتیبان کلید @@ -1100,7 +1100,7 @@ موفق! بازگشت به ورود هشدار - ایمیل خود را وارد کنید + تنظیم نشانی رایانامه رایانامه رایانامه (اختیاری) بعدی @@ -1334,8 +1334,8 @@ اطّلاعات رویداد اطّلاعات رمزنگاری سرتاسری شاخه - فعال‌کردن رمزنگاری -\n(هشدار: نمی‌تواند دوباره غیر فعال شود!) + به کار انداختن رمزنگاری. +\n(هشدار: نمی‌تواند دوباره از کار بیفتد!) رمزنگاری در این اتاق از کار افتاده است. رونوشت از نشانی اتاق رونوشت از شناسهٔ اتاق @@ -1500,9 +1500,9 @@ المنت در پس زمینه همگام‌سازی می‌کند به گونه ای که منابع محدود دستگاه (باتری) حفظ می‌شود. \nبسته به شارژ گوشی شما، ممکن است همگام‌سازی توسط سیستم‌عامل به تعویق بیوفتد. روشن کردن صفحه برای ۳ ثانیه - • محتوای پیام در اعلان‌ها نمایش داده نمی‌شوند - • اعلان ها حاوی فرا داده و محتوای پیام هستند - • محتوای پیام اعلان به طور ایمن و مستقیم از سرور هیوا دریافت می‌شود + • آگاهی‌ها محتوای پیام را نشان نخواهند داد + • آگاهی‌ها شاکل فراداده و محتوای پیام هستند + • محتوای پیام آگاهی به طور ایمن و مستقیم از کارساز خانگی ماتریکس دریافت می‌شود • اعلان ها فقط حاوی فرا داده هستند • اعلان ها از طریق سرور Firebase ارسال می شوند اگر دستگاه برای مدتی از شارژر جدا باشد و از دستگاه نیز استفاده نشود، گوشی وارد حالت غیر هوشیار می‌شود. در این حالت از دسترسی برنامه‌ها به اینترنت جلوگیری می‌شود و همگام سازی و هشدارهای استاندارد آن‌ها به تعویق می‌افتد. @@ -1543,11 +1543,11 @@ تست‌ها را اجرا کن در حال تشخیص مشکل مطمئن شوید لینک فعال‌سازی‌ای را که به ایمیل شما ارسال شده، باز کرده‌اید. - %s حذف شود؟ - شماره تلفن‌های شما - هیچ آدرس ایمیلی تا کنون به حساب کاربری شما افزوده نشده است - آدرس ایمیل‌های شما - هیچ شماره تلفنی تا کنون به اکانت شما افزوده نشده است + برداشتن %s؟ + شماره تلفن‌ها + هیچ رایانامه‌ای به حسابتان افزوده نشده + نشانی‌های رایانامه + هیچ شماره تلفنی به حسابتان افزوده نشده قابلیت جستجو در اتاق‌های رمزشده هنوز پیاده‌سازی نشده است. نتیجه‌ای در پی نداشت فیلترکردن کاربران مسدود شده @@ -1565,7 +1565,7 @@ %d مورد پرونده‌ها - اعضا + افراد اطلاعات اتاق گواهی را تنها در صورتی تایید کنید که اثر انگشت آن با اثر انگشتی که ادمین سرور ارائه کرده‌است برابر باشد. گواهی سرور تغییر کرده‌است. ممکن است این اتفاق به دلیل تمدید گواهی سرور رخ داده باشد. توصیه می‌شود از ادمین سرور سوال کنید. @@ -1585,8 +1585,8 @@ شما نمی‌توانید این تغییر را بازگردانید. زیرا در حال ارتقای سطح کاربر دیگر به سطح خودتان هستید. \nآیا مطمئن هستید؟ شما در حال دسترسی به %s هستید. آیا می خواهید به این اتاق بپیوندید؟ - این دعوت به %s ارسال شده است، که هیچ ارتباطی با اکانت شما ندارد. -\nممکن است بخواهید با اکانت دیگری وارد شوید یا این ایمیل را به اکانت خود اضافه کنید. + این دعوت به %s ارسال شده که ارتباطی با این حساب ندارد. +\nممکن است بخواهید با حسابی دیگر وارد شده یا این رایانامه را به حسابتان بیفزایید. متاسفانه به دلیل عدم دسترسی، درخواست شما امکان پذیر نمی باشد هیوا می‌تواند با دیدن دفترچه تلفن شما کاربرهای دیگر هیوا را بر اساس ایمبل و شماره تلفنشان پیدا کند. \n @@ -1648,7 +1648,7 @@ %2$s و %1$s شما تنظیم شد. \n \nآن‌ها را در جای مطمئن و امن نگهداری کنید! درصورتی که همه‌ی نشست‌های خود را از دست بدهید، به این دو جهت رمزگشایی پیام‌های رمزشده‌ی قبلی و اطلاعات امن نیاز دارید. - تنظیم پشتیبان‌گیری از کلید + برپایی پشتیبان‌گیری از کلید همگام‌سازی کلید Self Signing همگام‌سازی کلید کاربر همگام‌سازی کلید اصلی @@ -1724,9 +1724,9 @@ دسترسی به پیام‌های رمزشده را از دست خواهید داد مگر اینکه برای بازیابی کلیدهای رمزگذاری خود، به حساب خود وارد شوید. آیا تمامی اطلاعات ذخیره‌شده در این دستگاه پاک شود؟ \nبرای دسترسی به اطلاعات و پیام‌های حساب خود، دوباره وارد شوید. - هشدار: اطلاعات شخصی شما (شامل کلید‌های رمزنگاری) همچنان روی این دستگاه ذخیره شده‌اند. + هشدار: داده‌های شخصیتان (شامل کلید‌های رمزنگاری) همچنان روی این افزاره ذخیره شده‌اند. \n -\nاگر نمی‌خواهید از این دستگاه استفاده کنید، یا می‌خواهید با اکانت دیگری وارد شوید، این اطلاعات را حذف کنید. +\nاگر کارتان با این افزاره تمام شده یا می‌خواهید به حساب دیگری وارد شوید، پاکشان کنید. برای بازیابی کلیدهای رمزگذاری ذخیره شده در این دستگاه، وارد حساب خود شوید. شما برای خواندن همه پیام‌های رمزشده‌ی خود در هر دستگاهی به این کلید‌ها نیاز دارید. ادمین سرور (%1$s) شما را از حسابتان خارج کرده‌است %2$s (%3$s). این می تواند به دلایل مختلف باشد: @@ -1888,7 +1888,7 @@ توضیح در مورد اتاق (اختیاری) نام اتاق پیام پاک شد - به نظر می‌رسد که شما در حال تلاش برای اتصال به یک سرور دیگر هستید. آیا می خواهید از اکانت خود خارج شوید؟ + به نظر می‌رسد تلاش می‌کنید تا به کارساز خانگی دیگری وصل شوید. می‌خواهید خارج شوید؟ برای بازنشانی گذرواژه‌ی خود نیاز به پیکربندی سرور هویت‌سنجی دارید. شما از سرور هویت‌سنجی استفاده نمی‌کنید خطای نامشخص @@ -1899,30 +1899,30 @@ SAS تطابق نداشت Hash تطابق نداشت نشست نمی تواند در مورد روش‌های فرآیند تائید: hash ، MAC یا روش SAS به توافق برسد - نشست از آن تعامل اطلاعی ندارد - زمان فرآیند تائید به پایان رسید - کاربر فرآیند تائید را لغو کرد - %s می‌خواهد نشست شما را تائید کند - درخواست فرآیند تائید - تائید تعاملی نشست - فرآیند تائید لغو شد. + نشست، اطّلاعی از آن تعامل ندارد + زمان فرایند تأیید به پایان رسید + کاربر تأیید را لغو کرد + %s می‌خواهد نشستتان را تأیید کند + درخواست تأیید + تأیید تعاملی نشست + تأیید لغو شد. \nدلیل: %s - طرف مقابل فرآیند تائید را لغو کرد. + طرف مقابل تأیید را لغو کرد. \n%s - از روش قدیمی برای فرآیند تائید استفاده کنید. - چیزی نمایش داده نشده‌است؟ هنوز تمام کلاینت‌ها از امکان تائید به روش تعاملی پشتیبانی نمی‌کنند. از تائید به روش قدیمی استفاده کنید. - شما با موفقیت این نشست را تائید کردید. - منتظر تائید طرف مقابل… - شما یک درخواست فرآیند تأئید داخلی دریافت کرده اید. - این نشست را با تصدیق اعداد زیر که روی صفحه‌ی طرف مقابل نیز ظاهر شده‌است، تائید کنید - با تأئید یکسان بودن شکلک‌های زیر در صفحه طرف مقابل، این نشست را تأئید کنید - با تأئید این نشست، آن را به عنوان معتمد علامت‌گذاری می‌کنیم و همچنین نشست خود را به عنوان معتمد برای طرف مقابل علامت‌گذاری می کنیم. - فرآیند تائید کلید + استفاده از تأیید قدیمی. + چیزی ظاهر نمی‌شود؟ هنوز تمامی کارخواه‌ها از تأیید تعاملی پشتیبانی نمی‌کنند. از تأیید قدیمی استفاده کنید. + این نشست را با موفّقیت تأیید کردید. + منتظر تأیید طرف مقابل… + درخواست تأییدی دریافت کردید. + با تأیید ظاهر شدن عددهای زیر روی صفحهٔ طرف مقابل، این نشست را تأیید کنید + با تأیید ظاهر شدن شکلک‌های زیر روی صفحهٔ طرف مقابل، این نشست را تأیید کنید + تأیید این نشست، آن را برای خودتان و طرف مقابل، به عنوان مطمئن علامت خواهد زد. + تأیید کلید نمایش درخواست درخواست لغو شد فهمیدم - محتوای گفتگوی امن شما با این کاربر رمزنگاری سرتاسر شده و امکان دسترسی به آن‌ها توسط فرد سومی مقدور نیست. - نشست تائید شد! + پیام‌های امن با این کاربر به صورت سرتاسری رمزنگاری شده و قابل خوانده شدن به دست دیگران نیست. + تأییدشده! این نشست را تأیید کنید تا به عنوان معتمد علامت‌گذاری شود. اعتماد به نشست‌ها هنگام استفاده از پیام های رمزشده به صورت سرتاسر ، به شما اطمینان بیشتری از امنیت گفتگو‌ها می‌دهد. درخواست فرآیند تأیید داخلی شروع فرآیند تایید کردن @@ -1975,17 +1975,17 @@ پشتیبان با %d کلید بازیابی شد. پشتیبان با %d کلید بازیابی شد. - رمزگشایی با این کلید بازیابی امکان‌پذیر نیست: لطفاً بررسی کنید که کلید بازیابی را به درستی وارد کرده‌اید. - رمزگشایی پیام‌های قبلی - بارگذاری کلید‌ها… - در حال دریافت کلید‌های بازیابی… - بازیابی نسخه پشتیبان: + پشتیبان نتپانست با این کلید بازیابی رمزگشایی شود: لطفاً تأیید کنید که کلید بازیابی درستی را وارد کرده‌اید. + قفل‌گشایی تاریخچه + بارگذاری کردن کلید‌ها… + بارگیری کردن کلید‌ها… + بازیابی پشتیبان: خطای شبکه: لطفاً اتصال خود را بررسی کنید و دوباره امتحان کنید. رمزگشایی با این کلید امنیتی امکان‌پذیر نیست: لطفاً بررسی کنید که کلید امنیتی را به درستی وارد کرده‌اید. کلید بازیابی خود را گم کرده‌اید؟ می‌توانید کلید جدیدی را در تنظیمات تنظیم کنید. از کلید بازیابی برای رمزگشایی پیام‌های رمزشده‌ی قبلی خود استفاده کنید کلید امنیتی خود را نمی‌دانید؟ شما می‌توانید %s. - از کلید امنیتی برای رمزگشایی پیام‌های رمزشده‌ی قبلی خود استفاده کنید + برای قفل‌گشایی تاریخچهٔ پیام‌های رمزشده‌تان از عبارت عبور بازیابیتان استفاده کنید اگر از دستگاه خارج شوید یا دستگاه خود را از دست دهید، ممکن است امکان دسترسی به پیام های خود را نداشته باشید. کلیدهای رمزگذاری شما اکنون در پس زمینه در حال پشتیبان‌گیری بر روی سرور است. تهیه نسخه‌ی پشتیبان اولیه ممکن است چند دقیقه طول بکشد. در حال تولید کلید پشتیبان با استفاده از کلید امنیتی، این ممکن است چند ثانیه زمان ببرد. @@ -2003,17 +2003,17 @@ ما یک نسخه رمزگذاری شده از کلیدهای شما را در سرور ذخیره خواهیم کرد. با استفاده از کلید امنیتی قوی، از نسخه‌ی پشتیبان خود محافظت کنید. \n \nبرای حداکثر امنیت، کلید امنیتی باید با رمز ورود حساب شما متفاوت باشد. - پیام‌های موجود در اتاق های رمزگذاری شده به صورت سرتاسری رمز و ایمن می‌شوند. کلیدهای این پیام‌ها فقط در دسترس شما و گیرنده‌(ها) می‌باشد. + پیام‌‌ها در اتاق‌های رمزشده، با رمزنگاری سرتاسری امن شده‌اند. فقط شما و گیرنده(ها) کلیدهای خواندم این پیام‌ها را دارید. \n -\nبرای جلوگیری از از دست دادن کلیدهای خود به طور ایمن از آنها پشتیبان تهیه کنید. +\nبرای جلوگیری از گم کردن کلیدهایتان، از آن‌ها به صورت امن، پشتیبان بگیرید. هیچ نشست ماتریکسی موجود نیست - اگر می خواهید المنت یک کلید بازیابی ایجاد کند، لطفاً کلید امنیتی را حذف کنید. - کلید امنیتی بسیار ضعیف است - لطفا کلید امنیتی را وارد کنید - کلید امنیتی یکسان نبود - ورود کلید امنیتی - تائید کلید امنیتی - ایجاد کلید امنیتی + اگر می خواهید المنت یک کلید بازیابی ایجاد کند، لطفاً عبارت عبور را حذف کنید. + عبارت عبور بیش از حد ضعیف است + لطفاً عبارت عبوری وارد کنید + عبارت عبور، مطابق نبود + ورود عبارت عبور + تأیید عبارت عبور + ایجاد عبارت عبور APK معتبر Google Play Services پیدا نشد. اعلان‌ها ممکن است به درستی کار نکنند. %d+ +%d @@ -2048,12 +2048,12 @@ فعال و غیرفعال کردن markdown نام مستعار شما را تغییر می‌دهد اخراج کاربر با شناسه داده شده - بیشتر درباره‌ی اتاق توضیح دهید + تنظیم موضوع اتاق ترک اتاق با نام مستعار داده‌شده به اتاق بپیوندید کاربر با شناسه داده شده را به این اتاق دعوت می کند کاربر با شناسه داده شده را غیر‌فعال می‌کند - سطح اختیارات و دسترسی کاربر را مشخص می‌کند + سطح قدرت کاربر را تعریف می‌کند کاربر با شناسه داده شده را رفع مسدودیت می کند کاربر با شناسه داده شده را مسدود می کند نمایش اقدام @@ -2061,10 +2061,10 @@ دستور ناشناخته: %s خطا در اجرای دستور قابلیت همایش تصویری در حال توسعه بوده و ممکن است به درستی کار نکند. - یک نشست تایید نشده، درخواست کلید‌های رمزنگاری را دارد. + یک نشست تایید نشده، کلید‌های رمزنگاری را درخواست می‌کند. \nنام نشست: %1$s \nآخرین بازدید: %2$s -\nاگر در دستگاه دیگری وارد اکانت خود نشده‌اید این درخواست را نادیده بگیرید. +\nاگر به نشست دیگری وارد نشده‌اید، این درخواست را نادیده بگیرید. نشست تایید نشده‌ی شما \\\'%s\\\' درخواست کلید‌های رمزنگاری را دارد. نشست جدید درخواست دریافت کلید‌های رمزنگاری را دارد. \nنام نشست: %1$s @@ -2080,7 +2080,7 @@ %d دعوت %d دعوت - آدرس سرور + نشانی کارساز خانگی این اتاق شامل نشست‌های تایید نشده هستند. \nهیچ تضمینی وجود ندارد این نشست‌های تائیدنشده متعلق به کاربرانی باشد که فکر می‌کنید. \nتوضیه می‌شود افراد نشست‌های خود را تائید کنند. هر چند در صورتی که تمایلی به این کار ندارید، همچنان می‌توانید پیام ارسال کنید. @@ -2105,27 +2105,27 @@ این اتاق در هیچ اجتماع خاصی قرار نگرفته‌است هر کسی که لینک اتاق را دارد ( حتی اگر کاربر مهمان باشد) هر کسی که لینک اتاق را دارد (به جز کاربران مهمان‌) - تنها افرادی که دعوت شده‌اند - برای لینک دادن به یک اتاق، آن اتاق باید آدرس داشته باشد. + تنها کسانی که دعوت شده‌اند + برای پیوند به یک اتاق، باید نشانی داشته باشد. اجتماع - پخش صدای شاتر دوربین - انتخاب کنید - منبع پیش‌فرض رسانه - انتخاب کنید + پخش صدای شاتر + گزینش + منبع رسانهٔ پیش‌گزیده + گزینش فشرده سازی پیش‌فرض رسانه اطلاعات اضافه: %s خطایی هنگام تایید شماره تلفن رخ داده است. - کد + رمز خطایی هنگام تایید شماره تلفن رخ داده است - کد فعال‌سازی را وارد کنید + رمز فعّال‌سازی‌ای را وارد کنید ما یک پیام کوتاه با کد فعال‌سازی ارسال کرده‌ایم. لطفاً این کد را در زیر وارد کنید. تایید شماره تلفن شماره تلفن نامعتبر برای کشور مورد نظر شماره تلفن - لطفا یک کشور را انتخاب نمائید + لطفاً کشوری را برگزینید کشور - انتخاب کشور + گزینش یک کشور آیا از حذف %1$s %2$s مطمئن هستید؟ خطایی در هنگام تایید ایمیل شما رخ داده است. این شماره تلفن قبلا استفاده شده‌است. @@ -2154,4 +2154,9 @@ ارسال داده های تجزیه و تحلیل تجزیه و تحلیل مجوز دادن + تخصیص شکست خورد. + قطع اتّصال از کارساز هویت %s؟ + پیام مستقیم + ارسال تاریخچهٔ درخواست‌های هم‌رسانی کلید + نتایج بیش‌تری نیست \ No newline at end of file From 71a70ffafa579da8513f7f8cc31b7a8650158e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20FERREIRA=20DE=20SOUSA?= Date: Fri, 13 Nov 2020 16:31:06 +0000 Subject: [PATCH 067/231] Translated using Weblate (French) Currently translated at 100.0% (1933 of 1933 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 e4ec77068c..26b09f1f2c 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2198,4 +2198,5 @@ Message direct Inclure l\'historique d\'échange de clés Plus aucun résultat + Exporter le rapport d\'audit \ No newline at end of file From 8e7d5ddfd491c15bc5b546993c7cedfb2ba668d4 Mon Sep 17 00:00:00 2001 From: XoseM Date: Sat, 14 Nov 2020 06:27:19 +0000 Subject: [PATCH 068/231] Translated using Weblate (Galician) Currently translated at 27.3% (529 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/gl/ --- vector/src/main/res/values-gl/strings.xml | 174 +++++----------------- 1 file changed, 35 insertions(+), 139 deletions(-) diff --git a/vector/src/main/res/values-gl/strings.xml b/vector/src/main/res/values-gl/strings.xml index 6ef0b9437d..45a6091093 100644 --- a/vector/src/main/res/values-gl/strings.xml +++ b/vector/src/main/res/values-gl/strings.xml @@ -1,16 +1,13 @@ - + gl ES - Tema claro Tema escuro Tema negro - - Sincronizando + Sincronizando… Notificacións con son Notificacións silenciosas - Mensaxes Sala Configuración @@ -18,9 +15,7 @@ Histórico Informar de erros Detalles da comunidade - Cargando… - Aceptar Cancelar Gardar @@ -40,7 +35,8 @@ A escoita de eventos Adiante Informar sobre contido - Conferencia en curso.\nÚnase con %1$s ou %2$s. + Conferencia en curso. +\nÚnete con %1$s ou %2$s voz vídeo Non se pode iniciar a chamada, inténteo máis tarde @@ -53,7 +49,6 @@ ou Convidar Fóra de liña - Saír Accións Saír @@ -67,53 +62,43 @@ Pechar Copiado ao portaretallos Desactivar - Confirmación Aviso - Inicio Favoritas Xente Salas Comunidades - Buscar salas Buscar favoritas Buscar a xente Buscar salas Buscar comunidades - Convites Baixa prioridade - Conversas Axenda de enderezos local Directorio de usuario Só contactos Matrix Sen conversas - "Non lle permitiu acceder aos contactos locais a Element " + Non lle permitiches a Element acceder ós contactos locais Sen resultados - Salas Directorio de salas Sen salas Sen salas públicas accesibles Enviar unha icona - Licenzas de terceiras partes - Descargar Falar Limpar - 1 usuario - %d usuarios + 1 usuaria + %d usuarias - Convidar Comunidades Sen grupos - Enviar informes Enviar informes de fallos Enviar captura de pantalla @@ -126,10 +111,8 @@ Enviouse o informe de erros correctamente Houbo un problema enviando o informe de erros (%s) Progreso (%s%%) - Enviar a Ler - Unirse a sala Nome de usuario Rexistrar @@ -138,25 +121,20 @@ URL do servidor local URL do servidor de identidade Buscar - Iniciar conversa Iniciar chamada de voz Iniciar videochamada - Seguro que quere comezar a conversar con %s? Seguro que quere comezar unha chamada de audio? Seguro que quere comezar unha chamada de vídeo? - Enviar ficheiros Enviar iconas Sacar unha foto ou vídeo Sacar foto Sacar vídeo - - Non ten ningún paquete de iconas activado. - -Quere engadir algún? - + Non tes paquetes de pegatinas activados. +\n +\nQueres engadir algún\? Acceder Rexistrarse Enviar @@ -187,9 +165,9 @@ Quere engadir algún? Esqueceu o contrasinal? Usar configuracións de servidor personalizadas (avanzado) Comprobe o seu correo para continuar co rexistro - O rexistro tanto co correo como co número non se acepta dentro desta api. Só se vai a ter en conta o número de teléfono. - -Pode engadir a dirección de correo na sección de configuración de perfil. + O rexistro simultáneo co email e o número de teléfono non está soportado ata que o api exista. Só se terá en conta o número de teléfono. +\n +\nPoderás engadir o teu email ó perfil nos axustes. Este servidor local quere asegurarse de que non é un robot Nome de usuario empregado Servidor local: @@ -200,22 +178,16 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Debe introducir un novo contrasinal. Fallo na verificación do enderezo de correo: asegúrese de ter picado na ligazón do correo Petición de chave enviada. - Enviar como Onte Hoxe - Chamar Continuar - Eliminar Rexeitar - Ir á primeira mensaxe non lida. - Deixar a sala Crear - En liña Fóra de liña En pausa @@ -235,13 +207,11 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Non ten permiso para comentar nesta sala 1 mensaxe nova - %d mensaxe nova + %d mensaxes novas - Confiar Desconectar Ignorar - Xente Ficheiros Configuracións @@ -252,14 +222,12 @@ Pode engadir a dirección de correo na sección de configuración de perfil.MENSAXES XENTE FICHEIROS - Convites Iniciar conversa Crear sala Unirse á sala Unirse á sala Buscando cartafol… - Todas as mensaxes (alto) Todas as mensaxes Só mencións @@ -274,20 +242,17 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Versión Copyright Política de privacidade - Correo electrónico Engadir enderezo correo electrónico Teléfono Engada número de teléfono Activar notificacións para esta conta Mensaxes enviadas por bot - Versión versións anteriores Limpar o caché Limpar o caché de imaxes Manter as imaxes - Configuracións dos usuarios Notificacións Outro @@ -296,14 +261,12 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Dispositivos Activar por defecto as vistas previas en liña de URL Mostrar sempre marcas de tempo - "Mostrar marcas de tempo con formato 12 horas (ex. 2:30pm)" + Mostrar marcas de tempo con formato 12-horas Desactivar a miña conta - Analytics Enviar datos de análises Element recolle información analítica anónima para permitirnos mellorar o aplicativo. - Si, quero axuda - + Si, quero axudar! ID Nome Nome do dispositivo @@ -312,55 +275,42 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Autenticación Contrasinal: Enviar - Idioma da Interface Verificación pendente Comprobe o seu correo electrónico e pulse na ligazón que contén. Unha vez feito iso prema continuar. - Xa se está a usar este correo - Xa se está a usar este teléfono - + Xa se está a usar este correo. + Xa se está a usar este teléfono. novo contrasinal confirmar o contrasinal Escolla un país - País Número de teléfono Verificación do teléfono Código - - Aura - 3 días 1 semana 1 mes Para sempre - Foto da sala Nome da sala Asunto Etiqueta de sala Marcado como: - Favorita Prioridade baixa Ningún - Notificacións Quen pode ler o histórico? Quen pode acceder a esta sala? - Calquera Só membros (desde o momento en que se selecciona esta opción) Só membros (desde que foron convidados) Só membros (desde que se uniron) - Só persoas que foron convidadas Calquera que coñeza o enderezo da sala, aparte das convidadas Calquera que coñeza a ligazón a sala, incluíndo as convidadas - Usuarios excluídos - Avanzado Enderezos Labs @@ -369,12 +319,9 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Formato de alias non válido Copiar a ID da sala Copiar a dirección da sala - Directorio Tema - Información do cifrado extremo-a-extremo - Información do evento ID de usuario Chave de identidade Curve25519 @@ -382,15 +329,13 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Algoritmo ID de sesión Fallo ao descifrar - Información do dispositivo do remitente Nome do dispositivo Nome - ID de dispositivo + ID de sesión Chave do dispositivo Validación pegada Ed25519 - Exportar chaves E2E da sala Exportar chaves da sala Exportar @@ -399,49 +344,40 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Importar chaves E2E da sala Importar chaves de sala Importar - Nunca enviar mensaxes cifradas aos dispositivos que non estean verificados neste dispositivo - + Non enviar mensaxes cifradas desde esta sesión a sesións non verificadas. SEN Verificar Verificados Omitidos - dispositivo descoñecido ningún - Verificar Retirar verificación Por na lista negra Quitar da lista negra - Verificar dispositivo Certifico que coinciden as chaves - A sala contén dispositivos descoñecidos Escoller unha sala principal O servidor podería non estar dispoñible ou sobrecargado Escriba un servidor local para saber cales son todas súas as salas públicas URL do servidor local - "Todas as salas do servidor %s " + Todas as salas do servidor %s Todas as salas de %s nativas - Escriba aquí… - - 1 notificación sen ler + %d notificación sen ler %d notificacións sen ler - 1 notificación sen ler + %d notificación sen ler %d notificacións sen ler - 1 sala + %d sala %d salas %1$s en %2$s - Buscar no histórico - Tamaño da letra Anana Pequena @@ -450,13 +386,11 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Máis grande Aínda máis grande Enorme - Fallou a creación do trebello - 1 trebello activo - %d trebellos activos + %d widget activo + %d widgets activos - Non se puido crear o trebello. Fallo ao enviar a petición. O nivel de poder ten que ser un enteiro positivo. @@ -469,63 +403,47 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Hai un parámetro que non é válido. Engadir aplicacións de Matrix Usar a cámara nativa - Iniciar verificación Compartir sen verificar Ignorar petición - Aviso! As chamadas de reunión poderían non ser totalmente estables xa que están en desenvolvemento. - Erro na orde Comandos que non se recoñecen: %s - Off Ruidoso - Mensaxe cifrada - Crear Crear comunidade Nome da comunidade Exemplo ID da comunidade exemplo - Inicio Xente Salas Sen usuarios - Salas Uniuse Convidada Filtrar os membros do grupo Filtrar as salas dos grupos - O administrador da comunidade non fixo unha descrición longa desta comunidade. - Foi expulsado de %1$s por %2$s Foi bloqueado de %1$s por %2$s Motivo: %1$s Volver a unirse Esquecer sala - Avatar de recepción Avatar de aviso Avatar - Revisar agora - Desactivar conta Para continuar introduza o seu contrasinal: Desactivar conta - Enviar voz - continuar con… Non se atopou unha aplicación que poida completar iso. - Enviouse un correo a %s. Unha vez abra a ligazón que contén prema abaixo. A URL ten que comezar con http[s]:// Non se puido acceder: erro da rede @@ -534,51 +452,40 @@ Pode engadir a dirección de correo na sección de configuración de perfil.Non se puido rexistrar Non se puido rexistrar: erro coa propiedade do correo electrónico Introduza unha URL válida - Usuario/contrasinal incorrecto JSON con defectos Non contiña unha JSON correcta Enviáronse demasiadas peticións Este nome de usuario xa se está a usar Unha ligazón de correo na que aínda non se premeu - Volver a pedir as chaves de cifrado do outro dispositivo seu. - Petición enviada Inicie Element noutro dispositivo que poida descifrar esta mensaxe e que despois desde alí lle poida enviar as chaves a este dispositivo. - - 1 cambio de membros - %d cambio de membros + 1 cambio de participantes + %d cambios de participantes - Autor Inicial Mediano Pequeno - Cancelar a descarga? Cancelar a subida? %d s %1$dm %2$ds - Nome da sala Tema da sala - Chamada establecida Chamada finalizada Chamando… Chamada entrante Videochamada entrante Chamada de audio entrante - Chamada en activo - + Chamada activa… Fallou a conexión desde o outro lado. Non se deu iniciado a cámara chamara respondida noutro lugar - Saque unha foto ou video Non foi posible gravar vídeo - Información Gardado Gardar nas descargas? @@ -586,11 +493,10 @@ Pode engadir a dirección de correo na sección de configuración de perfil.NON Unirse Vista previa - O convite envióuselle a %s, mais non é alguén que estea asociado con esta conta. -Seguramente queira conectarse cunha conta distinta, ou engadir este correo a súa conta. + Este convite enviouse a %s, que non está asociada a esta conta. +\nPodes conectarte cunha conta diferente, ou engadir este email á túa conta. unha sala Esta é unha vista previa desta sala. Desactiváronse as interaccións coa sala. - Nova conversa Engadir membro @@ -598,14 +504,12 @@ Seguramente queira conectarse cunha conta distinta, ou engadir este correo a sú %d participantes activos - 1 membro - %d membros + 1 participante + %d participantes 1 membro - Chamar DISPOSITIVOS - Deixar a sala Borrar desta sala Mensaxes non enviadas. %1$s ou %2$s agora? @@ -613,10 +517,8 @@ Seguramente queira conectarse cunha conta distinta, ou engadir este correo a sú Pegada (%s): CONVIDADO UNIUSE - Cancelar a subida Cancelar a descarga - UNIRSE DIRECTORIO FAVORITOS @@ -636,7 +538,6 @@ Seguramente queira conectarse cunha conta distinta, ou engadir este correo a sú • O contido da mensaxe das notificacións está provén dun xeito seguro e directo do servidor local de Matrix • As notificacións conteñen metadatos e os datos da mensaxe • As notificacións non van a mostrar o contido da mensaxe - Son das notificacións Activar notificacións para este dispositivo Petición de sincronización esgotada @@ -651,22 +552,17 @@ Seguramente queira conectarse cunha conta distinta, ou engadir este correo a sú Element pode estar agochado e seguir traballando na xestión das notificacións dun xeito seguro e privado (inda que iso podería afectar ao uso da batería). Outorgar permisos Escolla outra opción - Accedeuse como Servidor de identidade - Seguro que quere eliminar este tipo de notificacións? - O ID interno desta sala é Precisa saír da aplicación primeiro para poder activar o cifrado. Nunca enviar mensaxes cifradas aos dispositivos que non estean verificados nesta sala desde este dispositivo. - O cifrado está activado nesta sala. - " Para verificar que se pode confiar neste dispositivo, contacte co seu dono utilizando algún outro medio (ex. en persoa ou chamada de teléfono) e pregúntelle se a clave que ven nos axustes de usuario do se dispositivo coincide coa clave inferior:" + Comfirma comparando o seguinte cos Axustes de Usuaria na túa outra sesión: Engadiu un novo dispositivo «%s» que está a solicitar as chaves de cifrado. O seu dispositivo sen verificar «%s» está solicitando as chaves de cifrado. Para continuar usando o servidor %1$s ten que revisar primeiro os seus termos e condicións. Esquezan todas as mensaxes que eu enviara no momento en que elimine a miña conta. (Aviso: iso suporá que os seguintes participantes só verán unha versión incompleta das conversas.) Introduza o seu contrasinal. - - + \ No newline at end of file From daf1362d28dc5477fce4b8415228444772469744 Mon Sep 17 00:00:00 2001 From: notramo Date: Thu, 12 Nov 2020 19:12:47 +0000 Subject: [PATCH 069/231] Translated using Weblate (Hungarian) Currently translated at 94.9% (1835 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 68 +++++++++++------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index 2b1247d7e3..60171a30d1 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -188,7 +188,7 @@ A hívott fél nem vette fel. Médiacsatlakozás sikertelen A kamera nem készíthető elő - a hívást máshol vették fel + a hívás más eszközön lett felvéve Kép vagy videó készítése Videorögzítés sikertelen Információ @@ -206,8 +206,8 @@ A Elementnek engedélyre van szüksége a mikrofonod és kamerád eléréséhez, hogy videohívást tudj indítani. \n \nEngedélyezd a hozzáférést a következő felugró ablakon, hogy hívást tudj indítani. - A Element a névjegyekben lévő e-mail és telefonszám alapján képes felkutatni más Matrix felhasználókar. Ha egyetértesz a névjegyek ilyen célú megosztásával, kérlek engedélyezd a hozzáférést a következő felugró üzenetben. - A Element a névjegyekben lévő e-mail és telefonszám alapján képes felkutatni más Matrix felhasználókar. + A Element a névjegyekben lévő e-mail és telefonszám alapján képes felkutatni más Matrix felhasználókat. Ha egyetértesz a névjegyek ilyen célú megosztásával, kérlek engedélyezd a hozzáférést a következő felugró üzenetben. + A Element a névjegyekben lévő e-mail és telefonszám alapján képes felkutatni más Matrix felhasználókat. \n \nEgyetértesz a névjegyek ilyen célú megosztásával\? "Elnézést. A művelet nem lett végre hajtva hiányzó engedélyek miatt" @@ -505,7 +505,7 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törli.Eltávolítás feketelistáról Munkamenet hitelesítése Hogy ellenőrizni lehessen, hogy ez a munkamenet megbízható, kérlek használj más kommunikáció módot a tulajdonossal (pl.: személyesen vagy telefonon keresztül) és kérdezd meg hogy a kulcs amit lát a Felhasználói Beállítások alatt megegyezik-e az alábbi kulccsal: - Ha egyezik, nyomja meg a hitelesítés gombot. Ha nem, akkor valaki más elfogta ezt a munkamenetet és érdemes lenne tiltólistára tenni. A jövőben ez a hitelesítési mód kényelmesebbé lesz téve. + Ha nem egyeznek, akkor a kommunikáció biztonsága kompromittálva lehet. A jövőben ez a hitelesítési mód kényelmesebbé lesz téve. Hitelesítem, hogy a kulcsok egyeznek Szoba ismeretlen munkameneteket tartalmaz Ez a szoba ismeretlen munkameneteket tartalmaz. @@ -515,7 +515,7 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törli. Válassz egy szoba könyvtárat A szerver lehet nem elérhető vagy túltöltött - Írj be egy Matrix szervert hogy listázza belőle a nyilvános szobákat + Írj be egy Matrix szervert, az ott található nyilvános szobák listázásához Matrix szerver URL Összes szoba a %s szerveren Összes anyanyelvi %s szoba @@ -763,7 +763,7 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlés Matrix-kisalkalmazás-token törlése Ez a szoba le lett cserélve és már nem aktív A beszélgetés itt folytatódik - Ez a szoba a folytatása egy másik beszélgetésnek + Ez a szoba egy másik beszélgetés folytatása Régebbi üzenetek megjelenítéséhez kattints ide A műveletet a hiányzó engedélyek miatt nem lehet végrehajtani. @@ -803,7 +803,7 @@ Matrixban az üzenetek láthatósága hasonlít az e-mailre. Az üzenet törlés Erőforrás korlát túllépve Kapcsolatfelvétel az adminisztrátorral vedd fel a kapcsolatot a szolgáltatás adminisztrátorával - Ez a Matrix szerver túllépte valamely erőforrás korlátot így néhány felhasználó nem tud bejelentkezni. + Ez a Matrix szerver túllépte valamely erőforrás korlátot így néhány felhasználó nem tud majd bejelentkezni. Ez a Matrix szerver túllépte egyik erőforrás korlátját. Ez a Matrix szerver elérte a havi aktív felhasználói korlátját így néhány felhasználó nem tud bejelentkezni. Ez a Matrix szerver elérte a havi aktív felhasználói korlátját. @@ -963,9 +963,9 @@ Helyezd biztonságba a kulcsokat, hogy ne vesszenek el. A Visszaállítási Kulcs ide lett mentve: \'%s\'. Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törlik. - Kérlek készíts egy másolatot + Kérlek, készíts egy másolatot! Visszaállítási Kulcs megosztása… - Visszaállítási Kulcs készítése jelmondatból, ez néhány másodpercet is igénybe vehet. + Visszaállítási Kulcs készítése jelmondatból, ez néhány másodpercet igénybe vehet. Visszaállítási Kulcs Váratlan hiba Mentés elkezdődött @@ -1032,9 +1032,9 @@ Figyelmeztetés: ez a fájl törlésre kerülhet, ha az alkalmazást törlik.(Haladó) Kulcsok kimentése kézzel Védd a mentésedet jelmondattal. - "A kulcsaid másolatait titkosítva a Matrix szerverünkön fogjuk tárolni. Védd a mentést jelszóval a biztonság érdekében. - -A maximális biztonság eléréséhez, használja más jelmondatot mint amit a felhasználói fiókhoz használtál." + A kulcsaid másolatait titkosítva a Matrix szervereden fogjuk tárolni. Védd jelszóval a mentést, a biztonság érdekében. +\n +\nA maximális biztonság eléréséhez, használj más jelszót, mint amit a bejelentkezéshez használtál. Mentés készítése Vagy védd a mentésedet egy Visszaállítási Kulccsal amit tárolj biztonságos helyen. (Haladó) Beállítás Visszaállítási Kulccsal @@ -1043,7 +1043,7 @@ A maximális biztonság eléréséhez, használja más jelmondatot mint amit a f A Visszaállítási Kulcs egy biztosíték amit használhatsz a titkosított üzenet hozzáférések visszaállítására, ha a jelmondatot elfelejtetted. A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókezelő (vagy széf) A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókezelő (vagy széf) - Készítettem másolatot + Készítettem egy másolatot Megosztás Soha ne veszíts el titkosított üzenetet Kulcs Mentés használatának megkezdése @@ -1127,7 +1127,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Ellenőrzési kérés érkezett Munkamenet ellenőrzése és beállítás megbízhatónak. A partnerek munkameneteiben való megbízás megnyugtató lehet, ha végponttól végpontig titkosítást használsz. A munkamenet ellenőrzése megbízhatónak fogja jelezni az eszközt és a partnernél a te munkamenetedet szintén megbízhatónak fogja jelezni. - Munkamenet ellenőrzése az alábbi emodzsik a partner képernyőjén való megjelenésének megerősítésével történik + Munkamenet ellenőrzése azáltal, hogy összehasonlítjátok, hogy a másik felhasználó képernyőjén is ugyan azok az emojik jelennek-e meg. Munkamenet ellenőrzése az alábbi számok a partner képernyőjén való megjelenésének megerősítésével történik Bejövő ellenőrzési kérés érkezett. Kérés megjelenítése @@ -1307,7 +1307,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró A munkamenet nyilvános neve látható azoknál akikkel beszélgetsz Nem használsz Azonosítási Szervert Nincs beállítva azonosítási szerver amire a jelszó visszaállításához szükség van. - Úgy látszik másik matrix szerverhez szeretnél csatlakozni. Kijelentkezel\? + Úgy tűnik, másik Matrix szerverhez szeretnél csatlakozni. Ki szeretnél jelentkezni\? Azonosítási szerver Azonosítási szerverről lecsatlakozás Azonosítási szerver beállítása @@ -1430,18 +1430,18 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró %1$s hozzáférhetővé tette a szobát bárkinek, aki ismeri a linket. %1$s beállította, hogy a szobába csak meghívóval lehessen belépni. Olvasatlan üzenetek - Szabadítsd fel a kommunikációdat. + A te beszélgetésed. Vedd birtokba! Beszélgess másokkal közvetlenül vagy csoportosan Beszélgess bizalmasan, titkosítást használva Bővítsd és szabd testre a élményt Kezdj neki Válassz szervert Hasonlóan az e-mailhez, egy fiókod van, de bárkivel tudsz beszélgetni - Milliók csatlakoznak ingyen a legnagyobb nyilvános szerveren - Prémium üzemeltetés szervezetek részére + Csatlakozz a milliónyi felhasználóhoz a legnagyobb nyilvános szerveren + Prémium szerver üzemeltetés szervezetek részére Tudj meg többet - Más - Személyre szabott szerver cím beállítása + Egyéni + Másik szerver cím megadása Folytatás Csatlakozás ide: %1$s Csatlakozás Element Matrix Services hoz @@ -1452,7 +1452,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró SSO-val való folytatás Element Matrix Services Cím Cím - Prémium üzemeltetés szervezetek részére + Prémium szerverüzemeltetés szervezetek részére Add meg az általad használt Modular szerver, vagy a hozzá tartozó Element címét Add meg a szerver vagy Element címét amihez csatlakozni szeretnél Az oldal betöltésekor hiba történt: %1$s (%2$d) @@ -1529,13 +1529,13 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Látták: Kijelentkeztél - A következő okok miatt lehet: -\n -\n• Másik munkamenetedben megváltoztattad a jelszavadat. -\n -\n• Törölted ezt a munkamenetedet egy másik munkamenetből. -\n -\n• A matrix szerver adminisztrátora biztonsági okokból érvénytelenítette a hozzáférésed. + A következő okok miatt lehet: +\n +\n• Másik munkamenetedben megváltoztattad a jelszavadat. +\n +\n• Törölted ezt a munkamenetedet egy másik munkamenetből. +\n +\n• Az általad használt Matrix szerver adminisztrátora biztonsági okokból érvénytelenítette a hozzáférésed. Lépj be újra Kijelentkeztél Bejelentkezés @@ -1546,7 +1546,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Személyes adatok törlése Figyelmeztetés: A személyes adataid (beleértve a titkosító kulcsokat is) továbbra is az eszközön vannak tárolva. \n -\nHa az eszközt nem használod tovább vagy másik fiókba szeretnél bejelentkezni, töröld őket. +\nHa az eszközt nem használod tovább, vagy másik fiókba szeretnél bejelentkezni, töröld őket. Minden adat törlése Adat törlése Biztos vagy benne, hogy minden az eszközön tárolt adatot törölni szeretnél\? @@ -1554,7 +1554,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Elveszted a hozzáférésedet a titkosított üzeneteidhez ha nem jelentkezel be a titkosítási kulcsok visszaállításához. Adat törlése A jelenlegi munkamenet %1$s felhasználóhoz tartozik és %2$s azonosítási adatait adtad meg. Ez Element-ben nem támogatott. -\nElőször töröld az adatokat, majd a másik felhasználói fiókba lépj be. +\nElőször töröld az adatokat, utána lépj be a másik fiókba! A matrix.to linked hibás A leírás túl rövid Első szinkronizáció… @@ -1579,7 +1579,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Megbízhatatlan belépés Egyeznek Nem egyeznek - Hitelesítheted a felhasználót, ha megerősíted, hogy az alábbi egyedi emodzsik azok amik ugyanabban a sorrendben megjelentek a képernyőjén. + Hitelesítheted a felhasználót, ha megerősíted, hogy ugyan azok az emojik jelennek meg az ő képernyőjén is, ugyan abban a sorrendben. A legnagyobb biztonság érdekében használj megbízható kommunikációs csatornát vagy tedd meg személyesen. Keresd a zöld pajzsot, hogy biztos lehess abban, hogy a felhasználó megbízható. Bízz meg a szoba minden felhasználójában, hogy a szoba biztonságos lehessen. Nem biztonságos @@ -1607,9 +1607,9 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Kód beolvasása Nem lehet beolvasni Ha nem vagy ott személyesen akkor inkább hasonlítsd össze az emodzsikat - Ellenőrzés emodzsikkal + Ellenőrzés emojik összehasonlításával Ellenőrzés emodzsival - Ha az alábbi kódot nem tudod beolvasni, ellenőrizd a rövid egyedi emodzsik összehasonlításával. + Ha az alábbi kódot nem tudod beolvasni, ellenőrizd a felhasználót néhány egyedi emoji összehasonlításával. QR kód kép Ellenőrzés: %s Ellenőrizve: %s @@ -1797,7 +1797,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró \nHa nem akarsz Üzenet Jelszót beállítani, hozz létre inkább Üzenet Kulcsot. Az Visszaállítási Jelmondat beállításával biztonságba helyezheted és hozzáférhetsz a titkosított üzeneteidhez valamint a bizalomhoz. Titkosítás bekapcsolva - Ebben a szobában az üzenetek végpontok között titkosítottak. További információkért és ellenőrzéshez nyisd meg a felhasználók profilját. + Ebben a szobában az üzenetek végpontok között titkosítottak. További információkért és ellenőrzéshez nyisd meg a felhasználók profiljait! Titkosítás nincs engedélyezve A szobában használt titkosítás nem támogatott %s elkészítette és beállította a szobát. From fa3035f9cf80cd0bfb7eb30969433f02df74073a Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 12 Nov 2020 19:42:08 +0000 Subject: [PATCH 070/231] Translated using Weblate (Swedish) Currently translated at 100.0% (1933 of 1933 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 97075ac975..4f71783b20 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2187,4 +2187,8 @@ Rumsinställningar Rumsämne (valfritt) Rumsnamn + Exportera granskning + Direktmeddelande + Skicka historik över nyckeldelningsförfrågningar + Inga fler resultat \ No newline at end of file From b739aa35f23a992832ca0b283eb39b0cd00c5c3c Mon Sep 17 00:00:00 2001 From: sr093906 Date: Sat, 14 Nov 2020 12:00:39 +0000 Subject: [PATCH 071/231] Translated using Weblate (Chinese (Simplified)) Currently translated at 99.1% (1917 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 107 +++++++++--------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 17bf14161c..bcb449cabb 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -186,14 +186,16 @@ 此主服务器想确认您不是机器人 一封电子邮件已发送至 %s。点击了其中的链接后,请点击下面。 电子邮箱地址验证失败:请确保您已点击邮件中的链接 - 密码已重置。 您已从所有设备注销并且不再接受推送通知。要重新启用通知,请重新在相应设备上登录。 + 密码已重置。 +\n +\n您已经退出所有会话,将不再收到推送通知。要重新启用通知,请在每台设备上重新登录。 原始 %d 秒 通话已连接 通话正在连接… - 为了发送或保存附件,Element 需要访问您的图片和视频库。 - -请在接下来弹出的窗口中授权允许访问。 + 为了发送或保存附件,Element 需要访问您的图片和视频库。 +\n +\n请在接下来弹出的窗口中授权允许访问,以便应用能够从您的手机发送文件。 为了拍照或进行视频通话,Element 需要访问您的相机。 为了进行语音通话,Element 需要访问您的麦克风。 您试图访问聊天室 %s。您是否愿意加入这个聊天室? @@ -238,9 +240,9 @@ 设置 已邀请 已加入 - 您要隐藏所有来自这个用户的消息吗? - -注意,此操作会重启应用并将花费一些时间。 + 您要隐藏所有来自这个用户的消息吗? +\n +\n注意,此操作会重启应用并将花费一些时间。 取消上传 取消下载 收藏夹 @@ -268,13 +270,13 @@ 通知 已忽略的用户 通讯录权限 - 这个操作需要额外的身份认证。 -请输入您的密码以继续。 + 这个操作需要额外的身份认证。 +\n请输入您的密码以继续。 身份认证 当前密码 - 是否重新显示所有来自 %s 的消息? - -注意,此操作会重启应用并将花费一些时间。 + 是否重新显示所有来自 %s 的消息? +\n +\n注意,此操作会重启应用并将花费一些时间。 选择国家 国家 请选择一个国家 @@ -309,8 +311,8 @@ “%s” 不是有效的别名格式 这个聊天室已启用加密。 这个聊天室已禁用加密。 - 启用加密 -(警告:无法再禁用!) + 启用加密 +\n(警告:无法再禁用!) 目录 端对端加密信息 事件信息 @@ -353,9 +355,9 @@ 问题反馈发送失败(%s) 阅读 无效令牌 - 在出现相应的 API 前,暂不支持同时使用电子邮箱地址与手机号码注册。只有手机号码会被添加到此帐户。 - -您可在设置中将电子邮箱添加到你的账户。 + api存在之前,不支持同时通过电子邮件和电话号码进行注册。我们只考虑电话号码。 +\n +\n您可以在设置中添加您的电子邮件到您的个人资料。 用户名已被使用 无法注册:电子邮箱所有权验证失败 无法识别指定的访问令牌 @@ -378,17 +380,18 @@ " \n \n请在接下来弹出的窗口中授权允许访问。" - 为了进行视频通话,Element 需要访问您的相机和麦克风。 - -请在接下来弹出的窗口中授权允许访问。 + Element需要许可才能访问您的摄像机和麦克风来执行视频通话。 +\n +\n请在接下来弹出的窗口中授权允许访问。 对不起。因为权限不足,操作已取消 保存至下载? 移除 - 此邀请已发送至未与此账户关联的 %s。 您可能希望用一个不同的账户登录,或者把这个电子邮箱加入到这个账户。 + 此邀请已发送至未与此账户关联的 %s。 +\n您可能希望用一个不同的账户登录,或者把这个电子邮箱加入到你的账户。 这是此聊天室的预览。与聊天室的交互已禁用。 通话 - 您将不能撤销这个修改,因为您正在让这个用户和您拥有相同的特权级别。 -您确定吗? + 您将不能撤销这个修改,因为您正在让这个用户和您拥有相同的特权级别。 +\n您确定吗? 这可能意味着有人在恶意劫持您的通讯,或者您的手机不信任远程服务器的数字证书。 如果服务器管理员说这是正常的,请确保以下的指纹与管理员提供的指纹相符。 报告这个内容的原因 @@ -442,9 +445,9 @@ Element 需要访问您的通讯录,才能根据电子邮箱地址和手机号码查找其他 Matrix 用户。 请在接下来的弹出窗口中授权允许访问。 - Element 需要访问您的通讯录,才能根据电子邮箱地址和手机号码查找其他 Matrix 用户。 - -允许 Element 访问您的通讯录? + Element可以检查您的通讯录,以根据电子邮件和电话号码找到其他Matrix用户。 +\n +\n你同意为此目的分享你的通讯录吗\? 空闲 仅 Matrix 用户 您的手机信任的证书已被更改。这非常反常。建议您不要接受此新证书。 @@ -472,9 +475,7 @@ %s 已尝试在这个聊天室的时间线上加载一个特定的时间点,但无法找到它。 公开名称 为验证此设备是否可信,请通过其他方式(例如面对面交换或拨打电话)与其拥有者联系,并询问他们该设备的用户设置中的密钥是否与以下密钥匹配: - 如果匹配,请点击下面的验证按钮。 -\n如果不匹配,那么这可能说明其他人正在盗用此设备,而您应当将其拉入黑名单。 -\n未来,这个验证过程将会变得更加精致、巧妙一些。 + 如果它们不匹配,您通信的安全性可能会受到影响。 这个聊天室包含未经验证的未知设备。 \n这意味着无法保证该设备属于其声称的用户。 \n我们建议您在继续操作之前,先验证每个设备,但如果您愿意也可以不验证而重新发送消息。 @@ -701,11 +702,7 @@ 请允许资料分析以帮助我们改进 Element。 是的,我愿意帮助! 停用账户 - 这将使您的账户永远不再可用。您将不能登录,或使用相同的用户 ID 重新注册。您的账户将退出所有已加入的聊天室,身份服务器上的账户信息也会被删除。此操作是不可逆的。 - -停用您的账户不会默认忘记您发送的消息。如果您希望我们忘记您发送的消息,请勾选下面的选择框。 - -Matrix 中的消息可见性类似于电子邮件。我们忘记您的消息意味着您发送的消息不会被发给新注册或未注册的用户,但是已收到您的消息的注册用户依旧可以看到他们的副本。 + 请在我停用账户的同时忘记我发送的所有消息(警告:这将导致未来的用户看到残缺的对话) 请输入您的密码以继续: 停用账户 @@ -821,17 +818,17 @@ Matrix 中的消息可见性类似于电子邮件。我们忘记您的消息意 系统设置。 通知已在系统设置中启用。 通知已在系统设置中禁用。 -请检查系统设置。 +\n请检查系统设置。 打开设置 帐号设置。 您的帐号已启用通知。 - 您的帐号已禁用通知。 -请检查帐号设置。 + 您的账户已禁用通知。 +\n请检查账户设置。 启用 设备设置。 已为此设备启用通知。 - 已为此设备禁用通知。 -请检查 Element 设置。 + 此会话未启用通知。 +\n请检查 Element 设置。 启用 Play 服务检查 Google Play 服务的 APK 文件可用且为最新版本。 @@ -840,17 +837,17 @@ Matrix 中的消息可见性类似于电子邮件。我们忘记您的消息意 修复 Play 服务 Firebase 令牌 成功获取 FCM Token: -%1$s - FCM Token 获取失败: -%1$s +\n%1$s + FCM Token 获取失败: +\n%1$s 注册 Token FCM Token 已成功注册至主服务器。 - 将 FCM Token 注册至主服务器时失败: -%1$s + 未能将FCM Token 注册到主服务器: +\n %1$s 通知服务 通知服务正在运行。 - 通知服务尚未运行。 -请尝试重启本应用程序。 + 通知服务未在运行。 +\n请尝试重启本应用程序。 启动服务 调用系统相机应用而非使用 Element 内置的相机界面。 此选项需要第三方应用录制消息。 @@ -870,8 +867,8 @@ Matrix 中的消息可见性类似于电子邮件。我们忘记您的消息意 显示账户变动事件 包括头像与显示名称的变动。 后台连接 - Element需要保持一个低影响的后台连接才能保证可靠的通知。 -在下一个弹出窗口中,系统将提示您允许 Element 始终在后台运行,请点击“允许“。 + Element需要保持低影响的后台连接,以便获得可靠的通知。 +\n下一个屏幕中,系统将提示您允许 Element 始终在后台运行,请点击“允许“。 授予权限 在验证您的电子邮件地址时发生了一个错误。 密码 @@ -899,11 +896,11 @@ Matrix 中的消息可见性类似于电子邮件。我们忘记您的消息意 服务重启失败 服务将在设备重启后启动。 服务不会在设备重启后启动,在您打开 Element 一次之前您将不会收到消息通知。 - 对 Element 的后台限制已被关闭。此测试应在移动数据(无Wi-Fi)环境下进行。 -%1$s - 对 Element 的后台限制已开启。 -Element 在后台时的工作将被显著的限制,这可能会影响消息通知。 -%1$s + 已禁用对 Element 的后台限制。此测试应使用移动数据(非Wi-Fi)进行。 +\n%1$s + 已启用对 Element 的后台限制。 +\nElement 在后台时的工作将被显著地限制,这可能会影响消息通知。 +\n%1$s 关闭后台限制 Element 未被电池优化影响。 如果设备在未充电的情况下关屏静置一段时间,其将进入打盹模式(Doze)。这将阻止应用访问网络并延后其运行、同步、与响铃。 @@ -1371,7 +1368,7 @@ Element 在后台时的工作将被显著的限制,这可能会影响消息通 正在加密文件… 正在发送文件 (%1$s / %2$s) 正在下载文件 %1$s… - "文件 %1$s 已下载!" + 文件%1$s 已被下载! 消息编辑 未找到编辑 过滤对话… @@ -1922,7 +1919,7 @@ Element 在后台时的工作将被显著的限制,这可能会影响消息通 无法添加媒体文件到相册 无法保存媒体文件 选择新的账户密码… - 在您的其他设备上使用最新的 Element,Element Web,Element Desktop,Element iOS,Element for Android,或其他能够交叉签名的 Matrix 客户端 + 在您的其他设备上使用最新的 Element、Element Web、Element Desktop、Element iOS、Element for Android,或其他能够交叉签名的 Matrix 客户端 Element Web \nElement Desktop Element iOS From 032d46d8e62ad94b495fd0636beb6de60ae17e99 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Fri, 13 Nov 2020 15:51:26 +0000 Subject: [PATCH 072/231] Translated using Weblate (Persian) Currently translated at 100.0% (190 of 190 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/fa/ --- matrix-sdk-android/src/main/res/values-fa/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-fa/strings.xml b/matrix-sdk-android/src/main/res/values-fa/strings.xml index 042fda7ddd..11a786f5ac 100644 --- a/matrix-sdk-android/src/main/res/values-fa/strings.xml +++ b/matrix-sdk-android/src/main/res/values-fa/strings.xml @@ -181,8 +181,8 @@ نشانی‌های %1$s را به این اتاق افزودید. - نشانی %1$s ار از این اتاق برداشتید. - نشانی‌های %1$s ار از این اتاق برداشتید. + نشانی %1$s را از این اتاق برداشتید. + نشانی‌های %1$s را از این اتاق برداشتید. نشانی %1$s ار افزوده و %2$s را از این اتاق برداشتید. نشانی اصلی این اتاق را به %1$s تنظیم کردید. From a056cbd19f0166aad8f5b5a46040f94af4941b39 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Nov 2020 10:54:36 +0100 Subject: [PATCH 073/231] Registration: annoying error message scares every new user when they add an email (#2391) --- CHANGES.md | 1 + .../im/vector/app/features/login/AbstractLoginFragment.kt | 5 +++++ .../main/java/im/vector/app/features/login/LoginViewModel.kt | 1 - 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9387ae161e..80d606f57e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Bugfix 🐛: - Fix issue when restoring draft after sharing (#2287) - Fix issue when updating the avatar of a room (new avatar vanishing) - Discard change dialog displayed by mistake when avatar has been updated + - Registration: annoying error message scares every new user when they add an email (#2391) Translations 🗣: - 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 c6658925af..e3c1aa7b12 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 @@ -69,6 +69,11 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed { } override fun showFailure(throwable: Throwable) { + // Only the resumed Fragment can eventually show the error, to avoid multiple dialog display + if (!isResumed) { + return + } + when (throwable) { is Failure.Cancelled -> /* Ignore this error, user has cancelled the action */ 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 81d6a78123..1f47916538 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 @@ -207,7 +207,6 @@ class LoginViewModel @AssistedInject constructor( private fun handleCheckIfEmailHasBeenValidated(action: LoginAction.CheckIfEmailHasBeenValidated) { // We do not want the common progress bar to be displayed, so we do not change asyncRegistration value in the state currentTask?.cancel() - currentTask = null currentTask = registrationWizard?.checkIfEmailHasBeenValidated(action.delayMillis, registrationCallback) } From 4dff9316c20f259bfd8f08c2ba5beab9e04c3f35 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 12:53:32 +0000 Subject: [PATCH 074/231] Convert TagsService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/room/tags/TagsService.kt | 7 ++----- .../session/room/tags/DefaultTagsService.kt | 21 ++++--------------- .../home/room/list/RoomListViewModel.kt | 11 +++------- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt index 3278c640de..69fde61f90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/tags/TagsService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.room.tags -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This interface defines methods to handle tags of a room. It's implemented at the room level. */ @@ -26,10 +23,10 @@ interface TagsService { /** * Add a tag to a room */ - fun addTag(tag: String, order: Double?, callback: MatrixCallback): Cancelable + suspend fun addTag(tag: String, order: Double?) /** * Remove tag from a room */ - fun deleteTag(tag: String, callback: MatrixCallback): Cancelable + suspend fun deleteTag(tag: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt index 932cb5d67e..d6c02f0a49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DefaultTagsService.kt @@ -18,15 +18,10 @@ package org.matrix.android.sdk.internal.session.room.tags import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.tags.TagsService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultTagsService @AssistedInject constructor( @Assisted private val roomId: String, - private val taskExecutor: TaskExecutor, private val addTagToRoomTask: AddTagToRoomTask, private val deleteTagFromRoomTask: DeleteTagFromRoomTask ) : TagsService { @@ -36,21 +31,13 @@ internal class DefaultTagsService @AssistedInject constructor( fun create(roomId: String): TagsService } - override fun addTag(tag: String, order: Double?, callback: MatrixCallback): Cancelable { + override suspend fun addTag(tag: String, order: Double?) { val params = AddTagToRoomTask.Params(roomId, tag, order) - return addTagToRoomTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + addTagToRoomTask.execute(params) } - override fun deleteTag(tag: String, callback: MatrixCallback): Cancelable { + override suspend fun deleteTag(tag: String) { val params = DeleteTagFromRoomTask.Params(roomId, tag) - return deleteTagFromRoomTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + deleteTagFromRoomTask.execute(params) } } 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 b3af3b5e95..84652506cd 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 @@ -33,7 +33,6 @@ 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.tag.RoomTag -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.lang.Exception @@ -191,17 +190,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, action.tag.otherTag() ?.takeIf { room.roomSummary()?.hasTag(it).orFalse() } ?.let { tagToRemove -> - awaitCallback { room.deleteTag(tagToRemove, it) } + room.deleteTag(tagToRemove) } // Set the tag. We do not handle the order for the moment - awaitCallback { - room.addTag(action.tag, 0.5, it) - } + room.addTag(action.tag, 0.5) } else { - awaitCallback { - room.deleteTag(action.tag, it) - } + room.deleteTag(action.tag) } } catch (failure: Throwable) { _viewEvents.post(RoomListViewEvents.Failure(failure)) From d67029c42cfe9d76917d62f610c61b948506cd15 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 13:12:07 +0000 Subject: [PATCH 075/231] Convert ReportingService to suspend functions Signed-off-by: Dominic Fischer --- .../session/room/reporting/ReportingService.kt | 5 +---- .../room/reporting/DefaultReportingService.kt | 14 ++------------ .../home/room/detail/RoomDetailViewModel.kt | 17 +++++++++-------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/reporting/ReportingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/reporting/ReportingService.kt index 0ccdfd1d3c..a444e2346e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/reporting/ReportingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/reporting/ReportingService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.room.reporting -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This interface defines methods to report content of an event. */ @@ -28,5 +25,5 @@ interface ReportingService { * Report content * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-rooms-roomid-report-eventid */ - fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback): Cancelable + suspend fun reportContent(eventId: String, score: Int, reason: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt index 384c544ee0..cac87a9d30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/DefaultReportingService.kt @@ -18,14 +18,9 @@ package org.matrix.android.sdk.internal.session.room.reporting import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.reporting.ReportingService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultReportingService @AssistedInject constructor(@Assisted private val roomId: String, - private val taskExecutor: TaskExecutor, private val reportContentTask: ReportContentTask ) : ReportingService { @@ -34,13 +29,8 @@ internal class DefaultReportingService @AssistedInject constructor(@Assisted pri fun create(roomId: String): ReportingService } - override fun reportContent(eventId: String, score: Int, reason: String, callback: MatrixCallback): Cancelable { + override suspend fun reportContent(eventId: String, score: Int, reason: String) { val params = ReportContentTask.Params(roomId, eventId, score, reason) - - return reportContentTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + reportContentTask.execute(params) } } 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 102a0673d4..1f22406883 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 @@ -99,6 +99,7 @@ import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap import timber.log.Timber import java.io.File +import java.lang.Exception import java.util.UUID import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -1112,15 +1113,15 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleReportContent(action: RoomDetailAction.ReportContent) { - room.reportContent(action.eventId, -100, action.reason, object : MatrixCallback { - override fun onSuccess(data: Unit) { - _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) + viewModelScope.launch { + val event = try { + room.reportContent(action.eventId, -100, action.reason) + RoomDetailViewEvents.ActionSuccess(action) + } catch (failure: Exception) { + RoomDetailViewEvents.ActionFailure(action, failure) } - - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) - } - }) + _viewEvents.post(event) + } } private fun handleIgnoreUser(action: RoomDetailAction.IgnoreUser) { From 574d5055bd616520dd05c3440715742aa6c14560 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 14:19:47 +0000 Subject: [PATCH 076/231] Convert DraftService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/room/send/DraftService.kt | 6 +-- .../session/room/draft/DefaultDraftService.kt | 14 +++---- .../home/room/detail/RoomDetailViewModel.kt | 38 ++++++++++--------- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt index 116a60e323..a9481d71a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt @@ -17,8 +17,6 @@ package org.matrix.android.sdk.api.session.room.send import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional interface DraftService { @@ -26,12 +24,12 @@ interface DraftService { /** * Save or update a draft to the room */ - fun saveDraft(draft: UserDraft, callback: MatrixCallback): Cancelable + suspend fun saveDraft(draft: UserDraft) /** * Delete the last draft, basically just after sending the message */ - fun deleteDraft(callback: MatrixCallback): Cancelable + suspend fun deleteDraft() /** * Return the current draft or null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt index 92e16a3501..93fbfb4df0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt @@ -19,18 +19,14 @@ package org.matrix.android.sdk.internal.session.room.draft import androidx.lifecycle.LiveData import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.room.send.DraftService import org.matrix.android.sdk.api.session.room.send.UserDraft -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String, private val draftRepository: DraftRepository, - private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers ) : DraftService { @@ -43,14 +39,14 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private * The draft stack can contain several drafts. Depending of the draft to save, it will update the top draft, or create a new draft, * or even move an existing draft to the top of the list */ - override fun saveDraft(draft: UserDraft, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + override suspend fun saveDraft(draft: UserDraft) { + withContext(coroutineDispatchers.main) { draftRepository.saveDraft(roomId, draft) } } - override fun deleteDraft(callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + override suspend fun deleteDraft() { + withContext(coroutineDispatchers.main) { draftRepository.deleteDraft(roomId) } } 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 1f22406883..ffaeb1b157 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 @@ -476,22 +476,24 @@ class RoomDetailViewModel @AssistedInject constructor( * Convert a send mode to a draft and save the draft */ private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState { - when { - it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { - setState { copy(sendMode = it.sendMode.copy(action.draft)) } - room.saveDraft(UserDraft.REGULAR(action.draft), NoOpMatrixCallback()) - } - it.sendMode is SendMode.REPLY -> { - setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } - room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) - } - it.sendMode is SendMode.QUOTE -> { - setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } - room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) - } - it.sendMode is SendMode.EDIT -> { - setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } - room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft), NoOpMatrixCallback()) + viewModelScope.launch { + when { + it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { + setState { copy(sendMode = it.sendMode.copy(action.draft)) } + room.saveDraft(UserDraft.REGULAR(action.draft)) + } + it.sendMode is SendMode.REPLY -> { + setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } + room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, action.draft)) + } + it.sendMode is SendMode.QUOTE -> { + setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } + room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, action.draft)) + } + it.sendMode is SendMode.EDIT -> { + setState { copy(sendMode = it.sendMode.copy(text = action.draft)) } + room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, action.draft)) + } } } } @@ -778,7 +780,9 @@ class RoomDetailViewModel @AssistedInject constructor( } else { // Otherwise we clear the composer and remove the draft from db setState { copy(sendMode = SendMode.REGULAR("", false)) } - room.deleteDraft(NoOpMatrixCallback()) + viewModelScope.launch { + room.deleteDraft() + } } } From 0a318f618b2e466e0af801e31ad3796bcc48e5e8 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 18:20:44 +0000 Subject: [PATCH 077/231] Convert RawService to suspend functions Signed-off-by: Dominic Fischer --- .../matrix/android/sdk/api/raw/RawService.kt | 11 ++----- .../sdk/internal/raw/DefaultRawService.kt | 29 ++++--------------- .../raw/wellknown/ElementWellKnownExt.kt | 3 +- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt index 4e24a17047..19549a338e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.raw -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * Useful methods to fetch raw data from the server. The access token will not be used to fetched the data */ @@ -26,17 +23,15 @@ interface RawService { /** * Get a URL, either from cache or from the remote server, depending on the cache strategy */ - fun getUrl(url: String, - rawCacheStrategy: RawCacheStrategy, - matrixCallback: MatrixCallback): Cancelable + suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String /** * Specific case for the well-known file. Cache validity is 8 hours */ - fun getWellknown(userId: String, matrixCallback: MatrixCallback): Cancelable + suspend fun getWellknown(userId: String): String /** * Clear all the cache data */ - fun clearCache(matrixCallback: MatrixCallback): Cancelable + suspend fun clearCache() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt index be01366efa..5107ba5b50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt @@ -16,45 +16,28 @@ package org.matrix.android.sdk.internal.raw -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.raw.RawCacheStrategy import org.matrix.android.sdk.api.raw.RawService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import java.util.concurrent.TimeUnit import javax.inject.Inject internal class DefaultRawService @Inject constructor( - private val taskExecutor: TaskExecutor, private val getUrlTask: GetUrlTask, private val cleanRawCacheTask: CleanRawCacheTask ) : RawService { - override fun getUrl(url: String, - rawCacheStrategy: RawCacheStrategy, - matrixCallback: MatrixCallback): Cancelable { - return getUrlTask - .configureWith(GetUrlTask.Params(url, rawCacheStrategy)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String { + return getUrlTask.execute(GetUrlTask.Params(url, rawCacheStrategy)) } - override fun getWellknown(userId: String, - matrixCallback: MatrixCallback): Cancelable { + override suspend fun getWellknown(userId: String): String { val homeServerDomain = userId.substringAfter(":") return getUrl( "https://$homeServerDomain/.well-known/matrix/client", - RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false), - matrixCallback + RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) ) } - override fun clearCache(matrixCallback: MatrixCallback): Cancelable { - return cleanRawCacheTask - .configureWith(Unit) { - callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun clearCache() { + return cleanRawCacheTask.execute(Unit) } } diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt index 119be66f94..c1118e40cb 100644 --- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt +++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt @@ -18,10 +18,9 @@ package im.vector.app.features.raw.wellknown import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.raw.RawService -import org.matrix.android.sdk.internal.util.awaitCallback suspend fun RawService.getElementWellknown(userId: String): ElementWellKnown? { - return tryOrNull { awaitCallback { getWellknown(userId, it) } } + return tryOrNull { getWellknown(userId) } ?.let { ElementWellKnownMapper.from(it) } } From 75c105a400ef58546d4791e719657def6770595d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Nov 2020 14:56:42 +0100 Subject: [PATCH 078/231] Move "Enable Encryption" from room setting screen to room profile screen (#2394) --- CHANGES.md | 1 + .../features/roomprofile/RoomProfileAction.kt | 1 + .../roomprofile/RoomProfileController.kt | 28 +++++++++++++++ .../roomprofile/RoomProfileFragment.kt | 20 +++++++++++ .../roomprofile/RoomProfileViewModel.kt | 36 +++++++++++++++++++ .../roomprofile/RoomProfileViewState.kt | 8 ++++- .../settings/RoomSettingsAction.kt | 1 - .../settings/RoomSettingsController.kt | 29 --------------- .../settings/RoomSettingsFragment.kt | 11 ------ .../settings/RoomSettingsViewModel.kt | 18 +--------- .../settings/RoomSettingsViewState.kt | 3 +- .../res/layout/fragment_matrix_profile.xml | 3 +- vector/src/main/res/values/strings.xml | 3 +- 13 files changed, 99 insertions(+), 63 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 936e6b0ffe..3ac90b4c71 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Improvements 🙌: - Open an existing DM instead of creating a new one (#2319) - Ask for explicit user consent to send their contact details to the identity server (#2375) - Handle events of type "m.room.server_acl" (#890) + - Move "Enable Encryption" from room setting screen to room profile screen (#2394) Bugfix 🐛: - Fix issue when restoring draft after sharing (#2287) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt index 85bc8773a5..073d30ff8e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt @@ -21,6 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState sealed class RoomProfileAction : VectorViewModelAction { + object EnableEncryption : RoomProfileAction() object LeaveRoom : RoomProfileAction() data class ChangeRoomNotificationState(val notificationState: RoomNotificationState) : RoomProfileAction() object ShareRoomProfile : RoomProfileAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 7dc744da31..891d15d04f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -28,6 +28,7 @@ import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject class RoomProfileController @Inject constructor( @@ -43,6 +44,7 @@ class RoomProfileController @Inject constructor( interface Callback { fun onLearnMoreClicked() + fun onEnableEncryptionClicked() fun onMemberListClicked() fun onBannedMemberListClicked() fun onNotificationsClicked() @@ -84,6 +86,7 @@ class RoomProfileController @Inject constructor( centered(false) text(stringProvider.getString(learnMoreSubtitle)) } + buildEncryptionAction(data.actionPermissions, roomSummary) // More buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) @@ -171,4 +174,29 @@ class RoomProfileController @Inject constructor( ) } } + + private fun buildEncryptionAction(actionPermissions: RoomProfileViewState.ActionPermissions, roomSummary: RoomSummary) { + if (!roomSummary.isEncrypted) { + if (actionPermissions.canEnableEncryption) { + buildProfileAction( + id = "enableEncryption", + title = stringProvider.getString(R.string.room_settings_enable_encryption), + dividerColor = dividerColor, + icon = R.drawable.ic_shield_black, + divider = false, + editable = false, + action = { callback?.onEnableEncryptionClicked() } + ) + } else { + buildProfileAction( + id = "enableEncryption", + title = stringProvider.getString(R.string.room_settings_enable_encryption_no_permission), + dividerColor = dividerColor, + icon = R.drawable.ic_shield_black, + divider = false, + editable = false + ) + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 5bd121d49b..bab64aebe9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -49,6 +49,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.media.BigImageViewerActivity import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_matrix_profile.* +import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import kotlinx.android.synthetic.main.view_stub_room_profile_header.* import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState import org.matrix.android.sdk.api.util.MatrixItem @@ -87,6 +88,7 @@ class RoomProfileFragment @Inject constructor( it.layoutResource = R.layout.view_stub_room_profile_header it.inflate() } + setupWaitingView() setupToolbar(matrixProfileToolbar) setupRecyclerView() appBarStateChangeListener = MatrixItemAppBarStateChangeListener( @@ -111,6 +113,11 @@ class RoomProfileFragment @Inject constructor( setupLongClicks() } + private fun setupWaitingView() { + waiting_view_status_text.setText(R.string.please_wait) + waiting_view_status_text.isVisible = true + } + private fun setupLongClicks() { roomProfileNameView.copyOnLongClick() roomProfileAliasView.copyOnLongClick() @@ -155,6 +162,8 @@ class RoomProfileFragment @Inject constructor( } override fun invalidate() = withState(roomProfileViewModel) { state -> + waiting_view.isVisible = state.isLoading + state.roomSummary()?.also { if (it.membership.isLeft()) { Timber.w("The room has been left") @@ -187,6 +196,17 @@ class RoomProfileFragment @Inject constructor( vectorBaseActivity.notImplemented() } + override fun onEnableEncryptionClicked() { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.room_settings_enable_encryption_dialog_title) + .setMessage(R.string.room_settings_enable_encryption_dialog_content) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ -> + roomProfileViewModel.handle(RoomProfileAction.EnableEncryption) + } + .show() + } + override fun onMemberListClicked() { roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomMembers) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index a78bf472d6..ec772ffcaa 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -28,12 +28,15 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.ShortcutCreator +import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.rx.RxRoom import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -65,6 +68,7 @@ class RoomProfileViewModel @AssistedInject constructor( val rxRoom = room.rx() observeRoomSummary(rxRoom) observeBannedRoomMembers(rxRoom) + observePermissions() } private fun observeRoomSummary(rxRoom: RxRoom) { @@ -82,8 +86,22 @@ class RoomProfileViewModel @AssistedInject constructor( } } + private fun observePermissions() { + PowerLevelsObservableFactory(room) + .createObservable() + .subscribe { + val powerLevelsHelper = PowerLevelsHelper(it) + val permissions = RoomProfileViewState.ActionPermissions( + canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + ) + setState { copy(actionPermissions = permissions) } + } + .disposeOnClear() + } + override fun handle(action: RoomProfileAction) { when (action) { + is RoomProfileAction.EnableEncryption -> handleEnableEncryption() RoomProfileAction.LeaveRoom -> handleLeaveRoom() is RoomProfileAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile() @@ -91,6 +109,24 @@ class RoomProfileViewModel @AssistedInject constructor( }.exhaustive } + private fun handleEnableEncryption() { + postLoading(true) + + viewModelScope.launch { + val result = runCatching { room.enableEncryption() } + postLoading(false) + result.onFailure { failure -> + _viewEvents.post(RoomProfileViewEvents.Failure(failure)) + } + } + } + + private fun postLoading(isLoading: Boolean) { + setState { + copy(isLoading = isLoading) + } + } + private fun handleCreateShortcut() { viewModelScope.launch(Dispatchers.IO) { withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt index 50723655bc..398982ede1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt @@ -26,8 +26,14 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary data class RoomProfileViewState( val roomId: String, val roomSummary: Async = Uninitialized, - val bannedMembership: Async> = Uninitialized + val bannedMembership: Async> = Uninitialized, + val actionPermissions: ActionPermissions = ActionPermissions(), + val isLoading: Boolean = false ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) + + data class ActionPermissions( + val canEnableEncryption: Boolean = false + ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index 80bb8813cf..f0a7b38478 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -25,7 +25,6 @@ sealed class RoomSettingsAction : VectorViewModelAction { data class SetRoomTopic(val newTopic: String) : RoomSettingsAction() data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction() - object EnableEncryption : RoomSettingsAction() object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 5231cc6b06..3c73e6ed46 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -29,7 +29,6 @@ import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibi import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -44,7 +43,6 @@ class RoomSettingsController @Inject constructor( // Delete the avatar, or cancel an avatar change fun onAvatarDelete() fun onAvatarChange() - fun onEnableEncryptionClicked() fun onNameChanged(name: String) fun onTopicChanged(topic: String) fun onHistoryVisibilityClicked() @@ -130,33 +128,6 @@ class RoomSettingsController @Inject constructor( editable = data.actionPermissions.canChangeHistoryReadability, action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() } ) - - buildEncryptionAction(data.actionPermissions, roomSummary) - } - - private fun buildEncryptionAction(actionPermissions: RoomSettingsViewState.ActionPermissions, roomSummary: RoomSummary) { - if (!actionPermissions.canEnableEncryption) { - return - } - if (roomSummary.isEncrypted) { - buildProfileAction( - id = "encryption", - title = stringProvider.getString(R.string.room_settings_addresses_e2e_enabled), - dividerColor = dividerColor, - divider = false, - editable = false - ) - } else { - buildProfileAction( - id = "encryption", - title = stringProvider.getString(R.string.room_settings_enable_encryption), - subtitle = stringProvider.getString(R.string.room_settings_enable_encryption_warning), - dividerColor = dividerColor, - divider = false, - editable = true, - action = { callback?.onEnableEncryptionClicked() } - ) - } } private fun formatRoomHistoryVisibilityEvent(event: Event): String? { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 57521f7d80..ab9d3c6896 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -127,17 +127,6 @@ class RoomSettingsFragment @Inject constructor( invalidateOptionsMenu() } - override fun onEnableEncryptionClicked() { - AlertDialog.Builder(requireActivity()) - .setTitle(R.string.room_settings_enable_encryption_dialog_title) - .setMessage(R.string.room_settings_enable_encryption_dialog_content) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.room_settings_enable_encryption_dialog_submit) { _, _ -> - viewModel.handle(RoomSettingsAction.EnableEncryption) - } - .show() - } - override fun onNameChanged(name: String) { viewModel.handle(RoomSettingsAction.SetRoomName(name)) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 086ce93bb0..05a75a585b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile -import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -28,7 +27,6 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import io.reactivex.Completable 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 @@ -118,8 +116,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_CANONICAL_ALIAS), canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_HISTORY_VISIBILITY), - canEnableEncryption = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_ENCRYPTION) + EventType.STATE_ROOM_HISTORY_VISIBILITY) ) setState { copy(actionPermissions = permissions) } } @@ -142,7 +139,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: override fun handle(action: RoomSettingsAction) { when (action) { - is RoomSettingsAction.EnableEncryption -> handleEnableEncryption() is RoomSettingsAction.SetAvatarAction -> handleSetAvatarAction(action) is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) } is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) } @@ -226,18 +222,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: ) } - private fun handleEnableEncryption() { - postLoading(true) - - viewModelScope.launch { - val result = runCatching { room.enableEncryption() } - postLoading(false) - result.onFailure { failure -> - _viewEvents.post(RoomSettingsViewEvents.Failure(failure)) - } - } - } - private fun postLoading(isLoading: Boolean) { setState { copy(isLoading = isLoading) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index f913bed382..2cadc8f798 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -47,8 +47,7 @@ data class RoomSettingsViewState( val canChangeName: Boolean = false, val canChangeTopic: Boolean = false, val canChangeCanonicalAlias: Boolean = false, - val canChangeHistoryReadability: Boolean = false, - val canEnableEncryption: Boolean = false + val canChangeHistoryReadability: Boolean = false ) sealed class AvatarAction { diff --git a/vector/src/main/res/layout/fragment_matrix_profile.xml b/vector/src/main/res/layout/fragment_matrix_profile.xml index c935ab5cee..c10185b2f3 100644 --- a/vector/src/main/res/layout/fragment_matrix_profile.xml +++ b/vector/src/main/res/layout/fragment_matrix_profile.xml @@ -95,7 +95,6 @@ - + + \ 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 48729df815..7eec4eca19 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2200,7 +2200,8 @@ Message editor - Enable end-to-end encryption + Enable end-to-end encryption… + You don\'t have permission to enable encryption in this room. Once enabled, encryption cannot be disabled. Enable encryption? From 3d970737d1942b6b5419561fde90c8462343c1a8 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Mon, 16 Nov 2020 14:45:42 +0000 Subject: [PATCH 079/231] Remove redundant return Signed-off-by: Dominic Fischer --- .../org/matrix/android/sdk/internal/raw/DefaultRawService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt index 5107ba5b50..3b0d7546e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt @@ -38,6 +38,6 @@ internal class DefaultRawService @Inject constructor( } override suspend fun clearCache() { - return cleanRawCacheTask.execute(Unit) + cleanRawCacheTask.execute(Unit) } } From 579efb016ae0e1873ac575252d59ab9afc7f1cad Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Nov 2020 17:25:42 +0100 Subject: [PATCH 080/231] Room creation form: add advanced section to disable federation (#1314) --- CHANGES.md | 1 + .../room/model/create/CreateRoomParams.kt | 21 +++++++- .../session/room/create/CreateRoomBody.kt | 4 +- .../room/create/CreateRoomBodyBuilder.kt | 2 +- .../app/core/resources/DrawableProvider.kt | 8 +-- .../features/form/FormAdvancedToggleItem.kt | 51 +++++++++++++++++++ .../createroom/CreateRoomAction.kt | 3 ++ .../createroom/CreateRoomController.kt | 19 +++++++ .../createroom/CreateRoomFragment.kt | 8 +++ .../createroom/CreateRoomViewModel.kt | 29 +++++++++++ .../createroom/CreateRoomViewState.kt | 3 ++ .../res/layout/item_form_advanced_toggle.xml | 34 +++++++++++++ .../src/main/res/layout/item_form_switch.xml | 2 - vector/src/main/res/values/strings.xml | 6 +++ 14 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt create mode 100644 vector/src/main/res/layout/item_form_advanced_toggle.xml diff --git a/CHANGES.md b/CHANGES.md index 936e6b0ffe..a3338b2c75 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Improvements 🙌: - Open an existing DM instead of creating a new one (#2319) - Ask for explicit user consent to send their contact details to the identity server (#2375) - Handle events of type "m.room.server_acl" (#890) + - Room creation form: add advanced section to disable federation (#1314) Bugfix 🐛: - Fix issue when restoring draft after sharing (#2287) 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 892a865751..80e3741a0c 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 @@ -94,7 +94,22 @@ class CreateRoomParams { * The server will clobber the following keys: creator. * Future versions of the specification may allow the server to clobber other keys. */ - var creationContent: Any? = null + val creationContent = mutableMapOf() + + /** + * Set to true to disable federation of this room. + * Default: false + */ + var disableFederation = false + set(value) { + field = value + if (value) { + creationContent[CREATION_CONTENT_KEY_M_FEDERATE] = false + } else { + // This is the default value, we remove the field + creationContent.remove(CREATION_CONTENT_KEY_M_FEDERATE) + } + } /** * The power level content to override in the default power level event @@ -120,4 +135,8 @@ class CreateRoomParams { fun enableEncryption() { algorithm = MXCRYPTO_ALGORITHM_MEGOLM } + + companion object { + private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" + } } 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 c30f11b9af..13d403e2e4 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 @@ -74,8 +74,8 @@ internal data class CreateRoomBody( val invite3pids: List?, /** - * Extra keys to be added to the content of the m.room.create. - * The server will clobber the following keys: creator. + * Extra keys, such as m.federate, to be added to the content of the m.room.create event. + * The server will clobber the following keys: creator, room_version. * Future versions of the specification may allow the server to clobber other keys. */ @Json(name = "creation_content") 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 632fcab70b..79ff9db087 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 @@ -81,7 +81,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( topic = params.topic, invitedUserIds = params.invitedUserIds, invite3pids = invite3pids, - creationContent = params.creationContent, + creationContent = params.creationContent.takeIf { it.isNotEmpty() }, initialStates = initialStates, preset = params.preset, isDirect = params.isDirect, diff --git a/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt b/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt index c184b04bd9..96b1cfbb8e 100644 --- a/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/DrawableProvider.kt @@ -26,12 +26,12 @@ import javax.inject.Inject class DrawableProvider @Inject constructor(private val context: Context) { - fun getDrawable(@DrawableRes colorRes: Int): Drawable? { - return ContextCompat.getDrawable(context, colorRes) + fun getDrawable(@DrawableRes drawableRes: Int): Drawable? { + return ContextCompat.getDrawable(context, drawableRes) } - fun getDrawable(@DrawableRes colorRes: Int, @ColorInt color: Int): Drawable? { - return ContextCompat.getDrawable(context, colorRes)?.let { + fun getDrawable(@DrawableRes drawableRes: Int, @ColorInt color: Int): Drawable? { + return ContextCompat.getDrawable(context, drawableRes)?.let { ThemeUtils.tintDrawableWithColor(it, color) } } diff --git a/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt b/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt new file mode 100644 index 0000000000..2d6535758e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/form/FormAdvancedToggleItem.kt @@ -0,0 +1,51 @@ +/* + * 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.form + +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +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.themes.ThemeUtils + +@EpoxyModelClass(layout = R.layout.item_form_advanced_toggle) +abstract class FormAdvancedToggleItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var title: CharSequence + @EpoxyAttribute var expanded: Boolean = false + @EpoxyAttribute var listener: (() -> Unit)? = null + + override fun bind(holder: Holder) { + super.bind(holder) + val tintColor = ThemeUtils.getColor(holder.view.context, R.attr.riotx_text_secondary) + val expandedArrowDrawableRes = if (expanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white + val expandedArrowDrawable = ContextCompat.getDrawable(holder.view.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) + } + holder.titleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) + holder.titleView.text = title + holder.view.setOnClickListener { listener?.invoke() } + } + + class Holder : VectorEpoxyHolder() { + val titleView by bind(R.id.itemFormAdvancedToggleTitleView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt index 4b3eacffaa..61799a741f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt @@ -27,6 +27,9 @@ sealed class CreateRoomAction : VectorViewModelAction { data class SetIsInRoomDirectory(val isInRoomDirectory: Boolean) : CreateRoomAction() data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction() + object ToggleShowAdvanced : CreateRoomAction() + data class DisableFederation(val disableFederation: Boolean) : CreateRoomAction() + object Create : CreateRoomAction() object Reset : CreateRoomAction() } 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 d1cc884336..157e192668 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 @@ -27,6 +27,7 @@ import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.discovery.settingsSectionTitleItem +import im.vector.app.features.form.formAdvancedToggleItem import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.form.formSubmitButtonItem @@ -148,6 +149,22 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin listener?.setIsEncrypted(value) } } + formAdvancedToggleItem { + id("showAdvanced") + title(stringProvider.getString(if (viewState.showAdvanced) R.string.hide_advanced else R.string.show_advanced)) + expanded(!viewState.showAdvanced) + listener { listener?.toggleShowAdvanced() } + } + if (viewState.showAdvanced) { + formSwitchItem { + id("federation") + enabled(enableFormElement) + title(stringProvider.getString(R.string.create_room_disable_federation_title, viewState.homeServerName)) + summary(stringProvider.getString(R.string.create_room_disable_federation_description)) + switchChecked(viewState.disableFederation) + listener { value -> listener?.setDisableFederation(value) } + } + } formSubmitButtonItem { id("submit") enabled(enableFormElement) @@ -165,6 +182,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin fun setIsInRoomDirectory(isInRoomDirectory: Boolean) fun setIsEncrypted(isEncrypted: Boolean) fun retry() + fun toggleShowAdvanced() + fun setDisableFederation(disableFederation: Boolean) fun submit() } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 88b8a65a1c..729c97370c 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -110,6 +110,14 @@ class CreateRoomFragment @Inject constructor( viewModel.handle(CreateRoomAction.SetIsEncrypted(isEncrypted)) } + override fun toggleShowAdvanced() { + viewModel.handle(CreateRoomAction.ToggleShowAdvanced) + } + + override fun setDisableFederation(disableFederation: Boolean) { + viewModel.handle(CreateRoomAction.DisableFederation(disableFederation)) + } + override fun submit() { viewModel.handle(CreateRoomAction.Create) } 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 57af95b107..fcb98916cc 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 @@ -53,9 +53,18 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr } init { + initHomeServerName() initAdminE2eByDefault() } + private fun initHomeServerName() { + setState { + copy( + homeServerName = session.myUserId.substringAfter(":") + ) + } + } + private var adminE2EByDefault = true private fun initAdminE2eByDefault() { @@ -99,9 +108,27 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action) is CreateRoomAction.Create -> doCreateRoom() CreateRoomAction.Reset -> doReset() + CreateRoomAction.ToggleShowAdvanced -> toggleShowAdvanced() + is CreateRoomAction.DisableFederation -> disableFederation(action) }.exhaustive } + private fun disableFederation(action: CreateRoomAction.DisableFederation) { + setState { + copy(disableFederation = action.disableFederation) + } + } + + private fun toggleShowAdvanced() { + setState { + copy( + showAdvanced = !showAdvanced, + // Reset to false if advanced is hidden + disableFederation = disableFederation && !showAdvanced + ) + } + } + private fun doReset() { setState { // Delete temporary file with the avatar @@ -151,6 +178,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE // Public room preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + // Disabling federation + disableFederation = state.disableFederation // Encryption if (state.isEncrypted) { 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 433cc02cc9..d1e5c0b1bd 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 @@ -28,6 +28,9 @@ data class CreateRoomViewState( val isPublic: Boolean = false, val isInRoomDirectory: Boolean = false, val isEncrypted: Boolean = false, + val showAdvanced: Boolean = false, + val disableFederation: Boolean = false, + val homeServerName: String = "", val hsAdminHasDisabledE2E: Boolean = false, val asyncCreateRoomRequest: Async = Uninitialized ) : MvRxState { diff --git a/vector/src/main/res/layout/item_form_advanced_toggle.xml b/vector/src/main/res/layout/item_form_advanced_toggle.xml new file mode 100644 index 0000000000..46609b64ca --- /dev/null +++ b/vector/src/main/res/layout/item_form_advanced_toggle.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_form_switch.xml b/vector/src/main/res/layout/item_form_switch.xml index 24425a5eb4..cc662680bb 100644 --- a/vector/src/main/res/layout/item_form_switch.xml +++ b/vector/src/main/res/layout/item_form_switch.xml @@ -15,8 +15,6 @@ android:layout_marginStart="@dimen/layout_horizontal_margin" android:layout_marginEnd="@dimen/layout_horizontal_margin" android:duplicateParentState="true" - android:ellipsize="end" - android:maxLines="1" android:textColor="?riotx_text_primary" android:textSize="15sp" app:layout_constraintBottom_toTopOf="@+id/formSwitchSummary" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 48729df815..5768750021 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2094,6 +2094,12 @@ "Enable encryption" "Once enabled, encryption cannot be disabled." + Show advanced + Hide advanced + + Block anyone not part of %s from ever joining this room + You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later. + Your email domain is not authorized to register on this server Untrusted sign in From b24608891ed526381f6d043ce4005989476d22c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pol=20G=C3=B3mez=20Riquelme?= Date: Sun, 15 Nov 2020 13:45:46 +0000 Subject: [PATCH 081/231] Translated using Weblate (Catalan) Currently translated at 60.5% (1171 of 1933 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 7e88fb1a45..ed6128702b 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -1373,4 +1373,5 @@ Les tasques que l\'aplicació intenta fer estaran restringides agressivament men Cancel·la invitació Torna a la trucada Error SSL. + La trucada d\'Element ha fallat \ No newline at end of file From cf5d112e3161944eb8a756a8768881118246e9f8 Mon Sep 17 00:00:00 2001 From: Victor Cuadrado Juan Date: Sun, 15 Nov 2020 22:29:57 +0000 Subject: [PATCH 082/231] Translated using Weblate (Spanish) Currently translated at 100.0% (1933 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ --- vector/src/main/res/values-es/strings.xml | 310 +++++++++++++--------- 1 file changed, 184 insertions(+), 126 deletions(-) diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 59237e8c5a..edde547f1e 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -146,8 +146,8 @@ Utilizar opciones personalizadas del servidor (avanzado) Por favor consulta tu correo electrónico para continuar con el registro Todavía no es posible registrarse con correo electrónico y número telefónico a la vez, hasta que exista la API. Solo se tendrá en cuenta el número telefónico. - -Puedes añadir tu correo electrónico a tu perfil en ajustes. +\n +\nPuedes añadir tu correo electrónico a tu perfil en ajustes. Este Servidor Doméstico quiere asegurarse de que no eres un robot Nombre de usuario en uso Servidor Doméstico: @@ -190,7 +190,7 @@ Puedes añadir tu correo electrónico a tu perfil en ajustes. "¿Cancelar la descarga? ¿Cancelar la subida? %d s - %1$dm %2$ds + %1$dmin %2$dseg Ayer Hoy @@ -252,7 +252,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Has sido invitado por %s a unirte a esta sala Esta invitación fue enviada a %s, que no esta asociado a esta cuenta. -Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrónico a esta cuenta. +\nQuizás quieras iniciar sesión con otra cuenta, o añadir este correo electrónico a esta cuenta. Estás intentando acceder a %s. ¿Quieres unirte para participar en la discusión? una sala Esta es una vista previa de esta sala. Las interacciones dentro de la sala se han deshabilitado. @@ -285,8 +285,8 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón ID de Usuario, Nombre o correo electrónico Mencionar Mostrar Lista de Sesiones - No podrás deshacer este cambio porque estás promoviendo al usuario para tener el mismo nivel de autoridad que tú. -¿Estás seguro? + No podrás deshacer este cambio porque estás ascendiendo al usuario al mismo nivel de autoridad que tú. +\n¿Estás seguro\? ¿Seguro que quieres invitar a %s a esta conversación? Invitar por ID @@ -300,7 +300,7 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón %s está escribiendo… %1$s y %2$s están escribiendo… %1$s y %2$s y otros están escribiendo… - Enviar un mensaje cifrado… + Enviar un mensaje encriptado… Enviar un mensaje (sin cifrar)… Se perdió la conexión con el servidor. Los mensajes no se enviaron. ¿%1$s o %2$s ahora? @@ -333,9 +333,9 @@ Quizás quieras iniciar sesión con otra cuenta, o añadir este correo electrón SE UNIERON Motivo para reportar este contenido - ¿Quieres ocultar todos los mensajes de este usuario? - -Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de tiempo. + ¿Quieres ocultar todos los mensajes de este usuario\? +\n +\nTen en cuenta que esta acción reiniciará la aplicación y puede tardar algo de tiempo. Cancelar Subida Cancelar Descarga @@ -425,7 +425,7 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Visto por última vez %1$s @ %2$s Esta operación requiere autenticación adicional. -Para continuar, ingresa tu contraseña por favor. +\nPara continuar, introduce tu contraseña por favor. Autenticación Contraseña: Enviar @@ -444,9 +444,9 @@ Para continuar, ingresa tu contraseña por favor. Confirmar contraseña No se pudo actualizar la contraseña Tu contraseña ha sido actualizada - ¿Mostrar todos los mensajes de %s? - -Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de tiempo. + ¿Mostrar todos los mensajes de %s\? +\n +\nTen en cuenta que esta acción reiniciará la aplicación y puede tomar algo de tiempo. ¿Seguro que quieres eliminar este objetivo de notificaciones? ¿Seguro que quieres eliminar los %1$s %2$s? Elige un país @@ -495,11 +495,11 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Direcciones Laboratorios Estas son funcionalidades experimentales que pueden romperse de maneras inesperadas. Utilizar con precaución. - Cifrado de Extremo a Extremo - El Cifrado de Extremo a Extremo está activo - Necesitas cerrar sesión para poder habilitar el cifrado. + Encriptación de Extremo a Extremo + La encriptación de Extremo a Extremo está activa + Necesitas cerrar sesión para poder habilitar la encriptación. Cifrar solo a sesiones verificadas - Nunca enviar mensajes cifrados a sesiones sin verificar en esta sala desde esta sesión. + Nunca enviar mensajes encriptados a sesiones sin verificar en esta sala desde esta sesión. Esta sala no tiene direcciones locales Dirección nueva (ej. #foo:matrix.org) @@ -511,23 +511,23 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Dejar de Establecer como Dirección Principal Copiar ID de Sala Copiar Dirección de Sala - El cifrado está habilitado en esta sala. - El cifrado está deshabilitado en esta sala. - Habilitar cifrado -(advertencia: ¡no se puede volver a deshabilitar!) + La encriptación está habilitada en esta sala. + La encriptación está deshabilitada en esta sala. + Habilitar encriptado +\n(advertencia: ¡no se puede volver a deshabilitar!) Directorio %s estaba intentando cargar un momento específico en la línea de tiempo de esta sala pero no pudo encontrarlo. - Información de cifrado de extremo a extremo + Información de encriptación Extremo-a-Extremo Información de eventos ID de Usuario Clave de identidad Curve25519 Clave de huella digital Ed25519 reclamada Algoritmo ID de Sesión - Error de descifrado + Error de desencriptación Información de la sesión emisora Nombre público Nombre público @@ -535,21 +535,21 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Clave de sesión Verificación Huella digital Ed25519 - Exportar claves de salas con Cifrado de Extremo a Extremo + Exportar claves de salas con encriptación Extremo-a-Extremo Exportar claves de sala Exportar las claves a un archivo local Exportar Ingresar frase de contraseña Confirmar frase de contraseña - Las claves de salas con Cifrado de Extremo a Extremo se guardaron en \'%s\'. - -Advertencia: este archivo puede ser eliminado si la aplicación se desinstala. - Importar claves de salas con Cifrado de Extremo a Extremo + Las claves de salas con encriptado de Extremo-a-Extremo se guardaron en \'%s\'. +\n +\nAdvertencia: este archivo puede ser eliminado si la aplicación se desinstala. + Importar claves de salas con encriptación Extremo-a-Extremo Importar claves de sala Importar las claves desde un archivo local Importar Cifrar solo a sesiones verificadas - Nunca enviar mensajes cifrados a sesiones sin verificar desde esta sesión. + Nunca enviar mensajes encriptados a sesiones sin verificar desde esta sesión. SIN Verificar Verificado Prohibido @@ -565,7 +565,11 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.Verifico que las claves coinciden La sala contiene sesiones desconocidas - Esta sala contiene sesiones desconocidas que no han sido verificados. Esto significa que no hay garantía de que las sesiones pertenezcan a los usuarios a los que dicen pertenecer. Recomendamos que pases por el proceso de verificación para cada sesión antes de continuar. Puedes reenviar el mensaje sin verificarlas si prefieres. Sesiones desconocidas: + Esta sala contiene sesiones desconocidas que no han sido verificadas. +\nEsto significa que no hay garantía de que las sesiones pertenezcan a los usuarios a los que dicen pertenecer. +\nRecomendamos que hagas el proceso de verificación por cada sesión antes de continuar. Pero puedes reenviar el mensaje sin verificarlas si prefieres. +\n +\nSesiones desconocidas: Selecciona un directorio de salas El servidor puede estar no disponible o sobrecargado @@ -631,8 +635,8 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.Añadir aplicaciones de Matrix Utilizar cámara nativa - Añadiste una nueva sesión \'%s\', que está solicitando claves de cifrado. - Tu sesión sin verificar \'%s\' está solicitando claves de cifrado. + Has añadido una nueva sesión \'%s\', que está solicitando claves de encriptación. + Tu sesión sin verificar \'%s\' está solicitando claves de encriptación. Iniciar verificación Compartir sin verificar Ignorar solicitud @@ -645,7 +649,7 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala. Desactivado Ruidoso - Mensaje cifrado + Mensaje encriptado Detalles de comunidad Cargando… Salir @@ -715,7 +719,7 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.%1$s salas encontradas para %2$s - 1 sala + %d sala %d salas @@ -728,16 +732,16 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.%d miembros activos - 1 mensaje sin leer + %d mensaje sin leer %d mensajes sin leer - 1 mensaje notificado sin leer + %d mensaje notificado sin leer %d mensajes notificados sin leer %1$s en %2$s - 1 componente activo + %d componente activo %d componentes activos @@ -752,10 +756,10 @@ Advertencia: este archivo puede ser eliminado si la aplicación se desinstala.Para continuar utilizando el servidor doméstico %1$s, debes revisar y aceptar los términos y condiciones. Revisar ahora Esto hará que tu cuenta quede permanentemente inutilizable. No podrás iniciar sesión, y nadie podrá volver a registrar la misma ID de usuario. Esto hará que tu cuenta salga de todas las salas en las cuales participa, y eliminará los datos de tu cuenta de tu servidor de identidad. Esta acción es irreversible. - -Desactivar tu cuenta no hace que por defecto olvidemos los mensajes que has enviado. Si quieres que olvidemos tus mensajes, por favor marca la casilla a continuación. - -La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Que olvidemos tus mensajes implica que los mensajes que hayas enviado no se compartirán con ningún usuario nuevo o no registrado, pero aquellos usuarios registrados que ya tengan acceso a estos mensajes seguirán teniendo acceso a su copia. +\n +\nDesactivar tu cuenta no hace que por defecto olvidemos los mensajes que has enviado. Si quieres que olvidemos tus mensajes, por favor marca la casilla a continuación. +\n +\nLa visibilidad de mensajes en Matrix es similar a la del correo electrónico. Que olvidemos tus mensajes implica que los mensajes que hayas enviado no se compartirán con ningún usuario nuevo o no registrado, pero aquellos usuarios registrados que ya tengan acceso a estos mensajes seguirán teniendo acceso a su copia. Por favor, olvida todos los mensajes enviados al desactivar mi cuenta (Advertencia: esto provocará que los usuarios futuros vean conversaciones incompletas) Para continuar, ingresa tu contraseña por favor: Desactivar Cuenta @@ -787,7 +791,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Borrar continuar con… Lo sentimos, no se encontró ninguna aplicación externa para completar esta acción. - Volver a solicitar las claves de cifrado de tus otras sesiones. + Volver a solicitar las claves de encriptado de tus otras sesiones. Solicitud de clave enviada. Privacidad de Notificaciones Element puede ejecutarse en segundo plano para gestionar tus notificaciones de forma segura y privada. Esto podría afectar la duración de la batería. @@ -846,11 +850,11 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu %d seleccionados - 1 miembro + %d miembro %d miembros - 1 sala + %d sala %d salas Límite de Recursos Excedido @@ -892,12 +896,12 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Copia de seguridad de la clave Usar copia de seguridad de la clave La copia de seguridad de la clave no ha finalizado, por favor espere… - No quiero mis mensajes cifrados + No quiero mis mensajes encriptados Creando copia de seguridad de las claves… Usar copia de seguridad de la clave ¿Estás seguro\? Copia de seguridad - Perderá el acceso a sus mensajes cifrados si cierra sesión sin hacer una copia de seguridad de sus claves. + Perderá el acceso a sus mensajes encriptados si cierra sesión sin hacer una copia de seguridad de sus claves. Quedarse Saltar Hecho @@ -937,7 +941,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Una o más pruebas han fallado, por favor prueba las soluciones propuestas. Una o más pruebas han fallado, por favor mándanos un informe de error para que podamos investigar. Copia de seguridad en progreso. Si cierras sesión ahora perderás el acceso a tus mensajes encriptados. - La copia de seguridad debería estar activa ahora en todas tus sesiones para evitar la pérdida del acceso a tus mensajes encriptados. + La copia de seguridad debería estar activa ahora en todas tus sesiones para evitar la pérdida de acceso a tus mensajes encriptados. Element usa los servicios de Google Play para entregar mensajes Push pero no parece estar configurado correctamente: \n%1$s solucionar error con los Servicios de Google Play @@ -962,10 +966,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Borrar copia de seguridad nueva copia de seguridad Ese era yo - nunca se pierden mensajes cifrados - Configurar copia de seguridad de las claves de cifrado - Nunca pierdas mensajes cifrados - Nuevos mensajes clave cifrados + Nunca pierda mensajes encriptados + Configurar copia de seguridad de las claves de encriptado + Nunca pierdas mensajes encriptados + Nuevas claves de encriptación de mensajes Gestionar Copia de Seguridad Guardando copia de seguridad… Versión @@ -1024,10 +1028,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Compresión predeterminada Seleccionar Seleccionar - Recuperación de mensajes cifrados + Recuperación de mensajes encriptados Gestionar copia de seguridad clave - %1$s: 1 mensaje + %1$s: %2$d mensaje %1$s: %2$d mensajes @@ -1040,7 +1044,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Nueva invitación Yo ** Error al enviar - por favor abra la sala - "Lo sentimos, las llamadas grupales con Jitsi no se pueden mantener en dispositivos antiguos (dispositivos con Android inferior a 5.0)" + Lo sentimos, las llamadas de grupo con Jitsi no están soportadas en dispositivos antiguos (dispositivos con Android inferior a 5.0) Iniciar la cámara del sistema en lugar de la pantalla de cámara personalizada. Esta opción requiere una aplicación de terceros para grabar los mensajes. El comando \"%s\" necesita mas parámetros o algunos parámetros son incorrectos. @@ -1061,8 +1065,8 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu La contraseña que has introducido es muy débil Por favor borra la contraseña si quieres que Element genere una clave de recuperación. No hay ninguna sesión de Matrix disponible - Nunca se pierden los mensajes cifrados - Los mensajes en salas cifradas están asegurados con cifrado de extremo a extremo. Solo los integrantes de la sala y tu podéis leer estos mensajes. + Nunca perder los mensajes encriptados + Los mensajes en salas encriptadas están asegurados con encriptación Extremo-a-Extremo. Solo los integrantes de la sala y tu podéis leer estos mensajes. \n \nAsegúrate de guardar bien tus claves para evitar perderlas. (Avanzado) @@ -1074,10 +1078,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Introduce una contraseña Creando copia de seguridad O, asegura tu copia de seguridad con una clave de recuperación, guardándola en algún lugar seguro. - "(Avanzado) preparar clave de recuperación" + (Avanzado) Establecer clave de recuperación Completado! Tus claves se están guardando. - Tu clave de recuperación es una red de seguridad - Puedes usarla para recuperar el acceso a tus mensajes cifrados si olvidas tu contraseña. + Tu clave de recuperación es una red de seguridad - puedes usarla para recuperar el acceso a tus mensajes encriptados si olvidas tu contraseña. \nMantén tu clave de recuperación en algún lugar muy seguro como un administrador de contraseñas (o en una caja fuerte) Mantén tu clave de recuperación en algún lugar muy seguro como un administrador de contraseñas (o en una caja fuerte) Hecho @@ -1097,10 +1101,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Tus claves cifradas están siendo guardadas en segundo plano en tu servidor. La copia de seguridad inicial podría tardar varios minutos. Estás seguro\? Podrías perder el acceso a tus mensajes si te desconectas o pierdes este dispositivo. - Utiliza tu clave de recuperación para desbloquear tu historial de mensajes cifrados + Utiliza tu clave de recuperación para desbloquear tu historial de mensajes encriptados Utiliza tu clave de recuperación No sabes tu clave de recuperación\? puedes %s. - Utiliza tu clave de recuperación para desbloquear tu historial de mensajes cifrados + Utiliza tu clave de recuperación para desbloquear tu historial de mensajes encriptados Introduzca la clave de recuperación Mensaje de recuperación Has perdido tu clave de recuperación\? Puedes crear una nueva en ajustes. @@ -1144,14 +1148,14 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \n%2$s Configuración de uso Origen predeterminado de medios - Configurar copia de seguridad de las claves de cifrado + Configurar copia de seguridad de las claves de encriptación Obteniendo una versión de copia de seguridad… La copia de seguridad tiene una firma valida de la sesión no verificada %s La copia de seguridad tiene una firma inválida de la sesión verificada %s La copia de seguridad tiene una firma inválida de la sesión no verificada %s Error al conseguir información de confianza para la copia de seguridad (%s). Para usar la copia de seguridad de la clave en esta sesión introduzca su contraseña o su clave de recuperación ahora. - Desea borrar sus claves cifradas guardadas del servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes cifrados. + Deseas borrar tus claves de encriptación guardadas en el servidor\? No podrás usar tu clave de recuperación para leer el historial de mensajes encriptados. Una nueva copia de seguridad de mensajes ha sido detectada. \n \nSi no ha establecido un nuevo método de recuperación, alguien podría estar intentando acceder a su cuenta. Cambie su contraseña y establezca un nuevo método de recuperación inmediatamente en ajustes. @@ -1159,13 +1163,13 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Reproducir sonido de cámara Verificar sesión ip desconocida - Una nueva sesión solicita claves de cifrado. -\nSesión: %1$s -\nVisto por última vez: %2$s + Una nueva sesión solicita claves de encriptación. +\nSesión: %1$s +\nVisto por última vez: %2$s \nSi no has iniciado sesión en otro dispositivo ignora esta solicitud. - Una sesión no verificada solicita claves de cifrado. -\nSesión: %1$s -\nVisto por última vez: %2$s + Una sesión no verificada solicita claves de encriptación. +\nSesión: %1$s +\nVisto por última vez: %2$s \nSi no has iniciado sesión en otro dispositivo ignora esta solicitud. Verificar Compartir @@ -1181,7 +1185,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Para más seguridad, te recomendamos que hagas esto en persona o por otros medios confiables. Empezar verificación Solicitud de verificación - Verifica esta sesión para marcarla como confiable. Confiar en sesiones de otros te da aún más tranquilidad cuando usas cifrado de mensajes de punto a punto. + Verifica esta sesión para marcarla de confianza. Marcar sesiones de otros como de confianza te da aún más tranquilidad cuando usas encriptacion de Extremo-a-Extremo. Verificar esta sesión la marcará como confiable, y también marcará como confiable tu sesión para la contraparte. Verifica esta sesión confirmando los emojis que aparecen en la pantalla de la contraparte Verifica esta sesión confirmando que los siguietes números aparecen en la pantalla de la contraparte @@ -1190,7 +1194,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Esperando confirmación de la contraparte… ¡Verificado! Has verificado correctamente esta sesión. - Los mensajes con este usuario están cifrados punto a punto y no son legibles por terceros. + Los mensajes con este usuario están encriptados de Extremo-a-Extremo y no son legibles por terceros. Ok ¿No aparece nada\? No todas las aplicaciones cliente soportan verificación interactiva. Usa la verificación clásica. Usar verificación clásica. @@ -1244,7 +1248,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Mensajes directos Nueva sala CREAR - Nombre de la sala + Nombre Público Cualquiera puede unirse a esta sala Directorio de salas @@ -1500,9 +1504,9 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Introduzca la dirección de Modular Element o servidor que quieres usar Introduzca la dirección del servidor Element al que quieres conectarte Se produjo un error al cargar la página: %1$s (%2$d) - "La aplicación no es capaz de iniciar sesión en este servidor. Este solo soporta el acceso mediante: %1$s. + La aplicación no es capaz de iniciar sesión en este servidor. Éste solo soporta el acceso mediante: %1$s. \n -\n¿Quieres acceder usando un cliente web\?" +\n¿Quieres acceder usando un cliente web\? Lo sentimos, este servidor no acepta nuevas cuentas. La aplicación no fue capaz de crear una cuenta en este servidor. \n @@ -1540,8 +1544,9 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Por favor, elija un nombre de usuario. Por favor, elija una contraseña. Verifica este enlace - Este link %1$s loredirecciona a otro sitio %2$s. . -\nEsta seguro de continuar\? + Este link %1$s lo redirecciona a otro sitio %2$s. +\n +\n¿Está seguro de continuar\? Adicionar miembros INVITAR Invitando usuarios… @@ -1602,7 +1607,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Element puede fallar con más frecuencia cuando ocurre un error inesperado Antepone ¯\\_(ツ)_/¯ a un mensaje de texto sin formato Habilitar encriptacion - Una vez habilitada, el cifrado no se puede deshabilitar. + Una vez habilitada, la encriptación no se puede deshabilitar. Su dominio de correo electrónico no está autorizado para registrarse en este servidor Inicio de sesión no confiable Coinciden @@ -1615,7 +1620,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Imagen. Audio Archivo - Sticker + Pegatina Esperando… %s cancelada Cancelado por usted @@ -1637,7 +1642,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Verificar %s Verificado %s Esperando por %s… - Los mensages en esta sala no están encriptados punto a punto. + Los mensajes en esta sala no están encriptados de Extremo-a-Extremo. Seguridad Saber mas Mas @@ -1671,9 +1676,9 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Línea de tiempo Editor de mensage Encriptar (end-to-end) - Una vez habilitado, el cifrado no se puede deshabilitar. + Una vez habilitada, la encriptación no se puede deshabilitar. Encriptar \? - Habilitar el cifrado + Habilitar la encriptación Firma cruzada Firma cruzada no habilitada Sesiones Activas @@ -1716,7 +1721,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Clave de mensaje Contraseña de la cuenta ¡Listo! - Cifrado habilitado + Encriptación habilitada Sala creada y configurada por usted. Esperando por %s… Ajuste de Notificaciones @@ -1728,7 +1733,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Confirmar PIN Resetear PIN Nuevo PIN - Para resetear su PIN, debe iniciar sección y crear uno nuevo. + Para resetear su PIN, debe iniciar sesión y crear uno nuevo. Establecer PIN Si decea resetear su PIN, toque Olvidé PIN para cerrar sesión y restablecer. Numeros telefonicos @@ -1768,7 +1773,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Configurar copia de seguridad segura Restablecer copia de seguridad segura Configurar en este dispositivo - Protéjase contra la pérdida de acceso a los mensajes y datos cifrados haciendo una copia de seguridad de las claves de cifrado en su servidor. + Protéjase contra la pérdida de acceso a los mensajes y datos encriptados haciendo una copia de seguridad de las claves de encriptado en su servidor. Genere una nueva llave de seguridad o establezca una nueva frase de seguridad para su copia de seguridad existente. Esto reemplazará su clave o frase actual. Las integraciones están deshabilitadas @@ -1790,7 +1795,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Sin widgets activos La clave de recuperación se ha guardada. Copia de seguridad segura - Protéjase contra la pérdida de acceso a mensajes y datos cifrados + Protéjase contra la pérdida de acceso a mensajes y datos encriptados Configurar copia de seguridad segura Mensaje borrado Se ha creado la sala, pero algunas invitaciones no se han enviado por el siguiente motivo: @@ -1820,7 +1825,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Email Nueva contraseña ¡Advertencia! - Cambiar su contraseña restablecerá cualquier clave de cifrado de extremo a extremo en todas sus sesiones, haciendo ilegible el historial de chat cifrado. Configure Key Backup o exporte las llaves de su sala desde otra sesión antes de restablecer su contraseña. + Cambiar su contraseña restablecerá cualquier clave de encriptado de Extremo-a-Extremo en todas sus sesiones, haciendo ilegible el historial de chat encriptado. Configure la Copia de seguridad de claves o exporte las claves de su sala desde otra sesión antes de restablecer su contraseña. Seguir Este correo electrónico no está vinculado a ninguna cuenta Revisa tu correo @@ -1845,12 +1850,12 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Utilice el formato internacional. Número de teléfono Numero de teléfono (opcional) - Próximo + Siguiente Confirmar número de teléfono Acabamos de mandar un codigo a %1$s. Ingréselo a continuación para verificar que es usted. Introduzca el código Enviar de nuevo - Próximo + Siguiente Utilice el formato internacional (el número de teléfono debe comenzar con \'+\') Los números de teléfono internacionales deben comenzar con \'+\' El número de teléfono parece no válido. Compruébelo por favor @@ -1858,7 +1863,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Nombre de usuario o correo electrónico Nombre de usuario Contraseña - Próximo + Siguiente Ese nombre de usuario está siendo usado Advertencia Tu cuenta aún no está creada. @@ -1885,7 +1890,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Si configura una cuenta en un servidor doméstico, use su ID de Matrix (por ejemplo, @user: dominio.com) y contraseña a continuación. ID de Matrix Si no conoce su contraseña, vuelva a restablecerla. - "Este no es un identificador de usuario válido. Formato esperado: \'@user:homeserver.org\'" + Éste no es un identificador de usuario válido. Formato esperado: \'@user:homeserver.org\' No se pudo encontrar un servidor de inicio válido. Por favor verifique su identificador Visto por Estás desconectado @@ -1899,44 +1904,46 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Iniciar sesión de nuevo Estás desconectado Registrarse - "El administrador de su servidor doméstico (%1$s) ha cerrado la sesión de su cuenta %2$s (%3$s)." - Inicie sesión para recuperar las claves de cifrado almacenadas exclusivamente en este dispositivo. Los necesita para leer todos sus mensajes seguros en cualquier dispositivo. + El administrador de su servidor privado (%1$s) ha cerrado la sesión de su cuenta %2$s (%3$s). + Inicie sesión para recuperar las claves de encriptación almacenadas exclusivamente en este dispositivo. Los necesita para leer todos sus mensajes seguros en cualquier dispositivo. Registrarse Contraseña Borrar datos personales - Advertencia: sus datos personales (incluidas las claves de cifrado) todavía se almacenan en este dispositivo. + Advertencia: sus datos personales (incluidas las claves de encriptación) todavía están almacenadas en este dispositivo. \n \nBórrelo si terminó de usar este dispositivo o si desea iniciar sesión en otra cuenta. Borrar todos los datos Borrar datos ¿Borrar todos los datos almacenados actualmente en este dispositivo\? \nVuelva a iniciar sesión para acceder a los datos y mensajes de su cuenta. - Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de cifrado. + Perderás el acceso a los mensajes seguros a menos que inicies sesión para recuperar tus claves de encriptación. Borrar datos - La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no es compatible con Element. Primero borre los datos, luego inicie sesión nuevamente con otra cuenta. + La sesión actual es para el usuario %1$s y usted proporciona las credenciales para el usuario %2$s. Esto no está suportado por Element. +\nPrimero borre los datos, luego inicie sesión nuevamente con otra cuenta. Su enlace matrix.to estaba mal formado El modo desarrollador activa funciones ocultas y también puede hacer que la aplicación sea menos estable. ¡Solo para desarrolladores! - Uno de los siguientes puede verse comprometido: -\n- Tu servidor doméstico -\n- El servidor doméstico al que está conectado el usuario que estás verificando -\n- La suya o la conexión a Internet de otros usuarios -\n- El suyo o el dispositivo de otros usuarios + Uno de los siguientes puede verse comprometido: +\n +\n- Tu servidor privado +\n- El servidor privado al que está conectado el usuario que estás verificando +\n- Su conexión a internet o la de otros usuarios +\n- Su dispositivo o el de otros usuarios Para mayor seguridad, verifique %s verificando un código único en ambos dispositivos. \n \nPara máxima seguridad, hágalo en persona. - Los mensajes de esta sala están cifrados de extremo a extremo. + Los mensajes de esta sala están encriptados de Extremo-a-Extremo. \n \nSus mensajes están protegidos con candados y solo usted y el destinatario tienen las claves únicas para desbloquearlos. Esta sesión no puede compartir esta verificación con sus otras sesiones. \nLa verificación se guardará localmente y se compartirá en una versión futura de la aplicación. Envía el emote dado coloreado como un arcoíris - Una vez habilitado, el cifrado de una sala no se puede deshabilitar. Los mensajes enviados en una sala cifrada no pueden ser vistos por el servidor, solo por los participantes de la sala. Habilitar el cifrado puede evitar que muchos bots y puentes funcionen correctamente. + Una vez habilitado, la encriptación de una sala no se puede deshabilitar. Los mensajes enviados en una sala encriptada no pueden ser vistos por el servidor, solo por los participantes de la sala. Habilitar la encriptación puede impedir que muchos bots y puentes funcionen correctamente. Para estar seguro, verifique %s comprobando un código de un solo uso. Para estar seguro, hágalo en persona o use otra forma de comunicarse. Compare los emoji únicos, asegurándose de que aparezcan en el mismo orden. Compare el código con el que se muestra en la pantalla del otro usuario. Los mensajes con este usuario están encriptados de extremo a extremo y no pueden ser leídos por terceros. - Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes cifrados y otros usuarios lo verán como de confianza. + Su nueva sesión ahora está verificada. Tiene acceso a sus mensajes encriptados y otros usuarios lo verán como de confianza. La firma cruzada está habilitada \n Claves privadas en el dispositivo. La firma cruzada está habilitada @@ -1944,7 +1951,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu \nNo se conocen las claves privadas La firma cruzada está habilitada. \nLas claves no son de confianza - El administrador de su servidor ha desactivado el cifrado de extremo a extremo de forma predeterminada en salas privadas y mensajes directos. + El administrador de su servidor ha desactivado la encriptación de Extremo-a-Extremo de forma predeterminada en salas privadas y mensajes directos. No hay información criptográfica disponible Esta sesión es confiable para mensajería segura porque usted la verificó: Verifique esta sesión para marcarla como confiable y otorgarle acceso a mensajes encriptados. Si no inició sesión en esta sesión, su cuenta puede verse comprometida: @@ -1952,8 +1959,8 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu %d sesión activa %d sesiones activas - Utilice una sesión existente para verificar esta, otorgándole acceso a los mensajes cifrados. - "Esta sesión es confiable para mensajería segura porque %1$s (%2$s) la verificó:" + Utilice una sesión existente para verificar ésta, otorgándole acceso a los mensajes encriptados. + Esta sesión es de confianza para mensajería segura porque %1$s (%2$s) la ha verificado: %1$s (%2$s) iniciado sesión con una nueva sesión: Hasta que este usuario confíe en esta sesión, los mensajes enviados hacia y desde ella se etiquetan con advertencias. Alternativamente, puede verificarlo manualmente. ¡Casi ahí! ¿Es %s muestra el mismo escudo\? @@ -1981,7 +1988,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Evento eliminado por el usuario, motivo: %1$s Evento moderado por el administrador de la sala, motivo: %1$s Solicitudes clave - Desbloquear el historial de mensajes cifrados + Desbloquear el historial de mensajes encriptados Utilice esta sesión para verificar su nuevo, otorgándole acceso a mensajes encriptados. Si cancela, no podrá leer mensajes encriptados en este dispositivo y otros usuarios no confiarán en él Si cancela, no podrá leer mensajes encriptados en su nuevo dispositivo y otros usuarios no confiarán en él @@ -1998,8 +2005,8 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Establecer un %s Generar una clave de mensaje Confirmar %s - "Ingrese su %s para continuar." - Proteja y desbloquee los mensajes cifrados y confíe en %s. + Ingrese su %s para continuar. + Proteja y desbloquee los mensajes encriptados y confíe en %s. Ingrese su %s nuevamente para confirmarlo. No use la contraseña de su cuenta. Ingrese una frase de seguridad que solo usted conozca, que se usa para proteger secretos en su servidor. @@ -2018,28 +2025,28 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Configuración de copia de seguridad de claves Tus %2$s y %1$s ahora están configurados. \n -\n¡Mantenlos a salvo! Los necesitará para desbloquear mensajes cifrados y proteger la información si pierde todas sus sesiones activas. +\n¡Mantenlos a salvo! Los necesitará para desbloquear mensajes encriptados y proteger la información si pierde todas sus sesiones activas. Imprímelo y guárdalo en un lugar seguro Guárdelo en una llave USB o unidad de respaldo Cópielo en su almacenamiento personal en la nube No puedes hacer eso desde el móvil - Establecer una frase de contraseña de recuperación le permite proteger y desbloquear mensajes cifrados y de confianza. + Establecer una frase de contraseña de recuperación le permite proteger y desbloquear mensajes encriptados y de confianza. \n \nSi no desea establecer una contraseña de mensaje, genere una clave de mensaje. - Establecer una frase de contraseña de recuperación le permite proteger y desbloquear mensajes cifrados y de confianza. - Si cancela ahora, puede perder mensajes y datos cifrados si pierde el acceso a sus inicios de sesión. + Establecer una frase de contraseña de recuperación le permite proteger y desbloquear mensajes encriptados y de confianza. + Si cancela ahora, puede perder mensajes y datos encriptados si pierde el acceso a sus inicios de sesión. \n \nTambién puede configurar la Copia de seguridad segura y administrar sus claves en Configuración. - Los mensajes de esta sala están cifrados de extremo a extremo. Obtenga más información y verifique a los usuarios en su perfil. - Cifrado no habilitado - El cifrado utilizado por esta sala no es compatible + Los mensajes de esta sala están encriptados de Extremo-a-Extremo. Obtenga más información y verifique a los usuarios en su perfil. + Encriptación no habilitada + La encriptación usada por esta sala no es compatible %s creado y configurado la sala. ¡Casi ahí! ¿El otro dispositivo muestra el mismo escudo\? ¡Casi ahí! Esperando confirmación… No se pudieron importar las claves Mensajes que contienen @room - Mensajes cifrados en chats uno a uno - Mensajes cifrados en chats grupales + Mensajes encriptados en chats 1:1 + Mensajes encriptados en chats de grupo Cuando las salas son actualizadas Solucionar problemas Envía un mensaje como texto estándar, sin interpretarlo como Markdown @@ -2083,14 +2090,14 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu La copia de seguridad no se pudo descifrar con esta clave de recuperación: verifique que ingresó la clave de recuperación correcta. No se pudo acceder al almacenamiento seguro Sin encriptar - Cifrado por un dispositivo no verificado + Encriptado por un dispositivo no verificado Revise dónde inició sesión Verifique todas sus sesiones para asegurarse de que su cuenta y sus mensajes estén seguros Verifique el nuevo inicio de sesión accediendo a su cuenta: %1$s Verificar manualmente por texto Verificación interactiva por emoji - Confirme su identidad verificando este inicio de sesión de una de sus otras sesiones, otorgándole acceso a los mensajes cifrados. - Confirme su identidad verificando este inicio de sesión, otorgándole acceso a los mensajes cifrados. + Confirme su identidad verificando este inicio de sesión de una de sus otras sesiones, otorgándole acceso a los mensajes encriptados. + Confirme su identidad verificando este inicio de sesión, otorgándole acceso a los mensajes encriptados. Marcar como de confianza Lo sentimos, esta operación aún no es posible para las cuentas conectadas mediante el inicio de sesión único. No pudimos crear tu DM. Marque los usuarios que desea invitar y vuelva a intentarlo. @@ -2112,7 +2119,7 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Enciende la cámara Configurar copia de seguridad segura Respaldo seguro - Protéjase contra la pérdida de acceso a los mensajes y datos cifrados haciendo una copia de seguridad de las claves de cifrado en su servidor. + Protéjase contra la pérdida de acceso a los mensajes y datos encriptados haciendo una copia de seguridad de las claves de encriptación en su servidor. Preparar Usa una llave de seguridad Genere una clave de seguridad para almacenar en un lugar seguro, como un administrador de contraseñas o una caja fuerte. @@ -2132,11 +2139,11 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu No puedes acceder a este mensaje Esperando este mensaje, esto puede tardar un poco No se puede descifrar - Debido al cifrado de extremo a extremo, es posible que deba esperar a que llegue el mensaje de alguien porque las claves de cifrado no se le enviaron correctamente. + Debido a la encriptación de Extremo-a-Extremo, es posible que deba esperar a que llegue el mensaje de alguien porque las claves de encriptación no se le enviaron correctamente. No puede acceder a este mensaje porque ha sido bloqueado por el remitente No puede acceder a este mensaje porque el remitente no confía en su sesión No puede acceder a este mensaje porque el remitente no envió las claves a propósito - Esperando el historial de cifrado + Esperando al historial de encriptación ¡Nos complace anunciar que hemos cambiado de nombre! Tu aplicación está actualizada y accediste a tu cuenta. Guardar la clave de recuperación en Agregar desde mi directorio telefónico @@ -2166,10 +2173,10 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu Mensaje directo Salir Preferencias - Los mensajes aquí están encriptados de punto a punto. + Los mensajes aquí están encriptados de Extremo-a-Extremo. \n \nTus mensajes están asegurados con un candado. Solo tú y tú destinatario tenéis las llaves especiales para desencriptarlos. - Los mensajes aquí no están encriptados de punto a punto. + Los mensajes aquí no están encriptados de Extremo-a-Extremo. Botones de Bot Encuesta Remover de Baja prioridad @@ -2194,4 +2201,55 @@ La visibilidad de mensajes en Matrix es similar a la del correo electrónico. Qu No posee permisos para iniciar una llamada en esta sala No posee permisos para iniciar una conferencia Resetear + Descartar cambios + Hay cambios sin salvar. ¿Descartar los cambios\? + La sala todavía no ha sido creada. ¿Cancelar la creación\? + El link está malformado + PIN es requerido cada vez que se abre Element. + PIN es necesario después de no usar Element por 2 minutos. + Requerir PIN después de 2 minutos + Sólo mostrar el número de mensajes no leídos en una notificación sencilla. + Mostrar detalles, como nombres de salas y contenido de mensajes. + Mostrar contenido de notificaciones + Element sólo puede ser desbloqueado via Código PIN. + Activar biometría de este dispositivo en particular, como huellas dactilares o reconocimiento facial. + Activar biometría + Configurar protecciones + Restringir acceso usando PIN y biométricos. + Restringir acceso + + Mostrar el dispositivo con el que puede verificar ahora + Mostrar %d dispositivos con los que puede verificar ahora + + Reiniciará sin historia, mensajes, dispositivos o usuarios verificados + Si resetea todo + Solo haga esto si no tiene otro dispositivo con el que verificar éste. + Resetear todo + ¿Ha perdido o olvidado todas las opciones de recuperación\? Resetear todo + Tú te has unido. + %s se ha unido. + Exportar inspección + ¿Borrar los datos de cuenta de typo %1$s\? +\n +\nPrecaución, puede causar funcionamiento inesperado. + Resultado de la verificación + Reaccionó con: %s + Este servidor particular usa una versión antigua. Pregunta a su administrador si puede actualizarlo. Puedes continuar usándolo, pero algunas características pueden no funcionar. + Tú has configurado como sólo con invitación. + %1$s ha configurado como sólo con invitación. + Añadir imagen de + Mostrar la historia completa en salas encriptadas + Ajustes de la sala + Tema + Tema de la sala (opcional) + Nombre de la sala + %1$s y %2$s + %1$s en %2$s y %3$s + + %d invitación + %d invitaciones + + Incluye invitar/unirse/expulsar/prohibir/mostrar cambios de nombre. + Mostrar eventos de los miembros de la sala + ¡La notificación ha sido cliqueada! \ No newline at end of file From 7223959fdac7d98662762519e0028e21c84d28ae Mon Sep 17 00:00:00 2001 From: Rob Johnson Date: Sun, 15 Nov 2020 21:36:15 +0000 Subject: [PATCH 083/231] Translated using Weblate (Spanish) Currently translated at 100.0% (1933 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/es/ --- vector/src/main/res/values-es/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index edde547f1e..a26c4c91d6 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -1481,7 +1481,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua %1$s ha hecho la sala pública para cualquier persona con el link. %1$s: Ahora la sala solo es accesible por invitación. Mensajes no leídos - Es tu conversación. Hazla tuya. + Es tu conversación. Sé su dueño. Envía mensajes a personas o grupos Mantén las conversaciones privadas con encriptación Extiende y personaliza tu experiencia @@ -1821,7 +1821,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Dirección de servicios de Element Matrix Ingrese la dirección del servidor que desea utilizar Se enviará un correo electrónico de verificación a su bandeja de entrada para confirmar la configuración de su nueva contraseña. - Próximo + Siguiente Email Nueva contraseña ¡Advertencia! @@ -1844,7 +1844,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Configure un correo electrónico para recuperar su cuenta. Más tarde, opcionalmente, puede permitir que las personas que conoce lo descubran mediante su correo electrónico. Correo electrónico Email (opcional) - Próximo + Siguiente Establecer número de teléfono Configure un número de teléfono para permitir que las personas que conoce lo descubran opcionalmente. Utilice el formato internacional. @@ -2183,8 +2183,8 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua Añadir a Baja prioridad Rotar y recortar - $d segundo - $d segundos + % segundo + %d segundos Por favor, haz click en la notificación. Si no la ves, por favor revisa las preferencias del sistema. Mostrar notificación From a8f2fd3f4b0f0cfa87fca805cc98f716f7c2ce84 Mon Sep 17 00:00:00 2001 From: AlexanderLevchenkoTechs Date: Mon, 16 Nov 2020 22:19:55 +0000 Subject: [PATCH 084/231] Translated using Weblate (Ukrainian) Currently translated at 43.6% (844 of 1933 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 22ebb6c885..fdc69678fd 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1002,4 +1002,20 @@ Резервне копіювання ключів… Якщо вийти зараз, ви втратите свої зашифровані повідомлення Перевірка сеансу + Стікер + Галерея + Файл + Додати зображення з + Контакт + Камера + Аудіо + Створити нову кімнату + Кімнати + Пошук в зашифрованих кімнатах не підтримується на даний момент. + Запитувати підтвердження перед початком дзвінку + Запобігати випадковим дзвінкам + Мені не потрібні мої зашифровані повідомлення + Скасувати зміни + SSL помилка. + Ви не можете здійснити дзвінок із самим собою, дочекайтесь, доки інші учасники приймуть ваше запрошення \ No newline at end of file From 14bf0038a921490af4d64571be7845bfdf9d2028 Mon Sep 17 00:00:00 2001 From: Victor Cuadrado Juan Date: Sun, 15 Nov 2020 22:57:08 +0000 Subject: [PATCH 085/231] Translated using Weblate (Spanish) Currently translated at 100.0% (190 of 190 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/es/ --- .../src/main/res/values-es/strings.xml | 104 +++++++----------- 1 file changed, 42 insertions(+), 62 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-es/strings.xml b/matrix-sdk-android/src/main/res/values-es/strings.xml index e2d09c7857..3648ca3a72 100644 --- a/matrix-sdk-android/src/main/res/values-es/strings.xml +++ b/matrix-sdk-android/src/main/res/values-es/strings.xml @@ -1,9 +1,7 @@ - + - %1$s: %2$s %1$s envió una imagen. - la invitación de %s %1$s invitó a %2$s %1$s te ha invitado @@ -30,61 +28,44 @@ todos los miembros de la sala. todos. desconocido (%s). - %1$s activó el cifrado de extremo a extremo (%2$s) - + %1$s ha activado la encriptación de Extremo-a-Extremo (%2$s) %1$s solicitó una conferencia de vozIP conferencia de vozIP iniciada conferencia de vozIP finalizada - (el avatar también se cambió) %1$s eliminó el nombre de la sala %1$s eliminó el tema de la sala %1$s actualizó su perfil %2$s %1$s invitó a %2$s a unirse a la sala %1$s aceptó la invitación para %2$s - ** No es posible descifrar: %s ** El dispositivo emisor no nos ha enviado las claves para este mensaje. - No se pudo redactar No es posible enviar el mensaje - No se pudo cargar la imagen - Error de red Error de Matrix - - - - Actualmente no es posible volver a unirse a una sala vacía. - - Mensaje cifrado - + Mensaje encriptado Dirección de correo electrónico Número telefónico - %1$s envió una pegatina. - Invitación de %s Invitación a Sala %1$s y %2$s Sala vacía - %1$s y 1 otro %1$s y %2$d otros - - Mensaje eliminado Mensaje eliminado por %1$s Mensaje eliminado [motivo: %1$s] @@ -98,10 +79,8 @@ \nImportando Comunidades Sincronización Inicial: \nImportando Datos de la Cuenta - Enviando mensaje… Borrar cola de envío - %1$s ha invitado a %2$s. Razón: %3$s %1$s te ha invitado. Razón: %2$s %1$s se ha unido. Razón: %2$s @@ -111,9 +90,7 @@ %1$s ha baneado a %2$s. Razón: %3$s %1$s ha aceptado la invitación para %2$s. Razón: %3$s %1$s ha eliminado la dirección principal para esta sala. - %s ha actualizado la sala. - Sincronización Inicial: \nImportando criptografía Sincronización Inicial: @@ -127,32 +104,25 @@ %1$s envió una invitación a %2$s para que se una a la sala. Razón: %3$s %1$s revocó la invitación de %2$s para unirse a la sala. Razón: %3$s %1$s ha retirado la invitación de %2$s. Razón: %3$s - %1$s ha añadido %2$s como alias de esta sala. %1$s ha añadido %2$s como alias de esta sala. - - %1$s ha quitado %2$s como alias de esta habitación. - %1$s ha quitado %2$s como alias de esta habitación. + %1$s ha quitado %2$s como alias de esta sala. + %1$s ha quitado %2$s como alias de esta sala. - %1$s ha establecido la dirección principal de esta sala a %2$s. %1$s ha permitido que los invitados se unan a la sala. %1$s ha impedido que los invitados se unan a la sala. - %1$s ha activado la encriptación extremo a extremo. %1$s ha activado la encriptación de extremo a extremo (algoritmo no reconocido %2$s). - %s solicita verificar su clave, pero su cliente no soporta la verificación de la clave en chat. Necesitará usar la verificación de claves clásica para poder verificar las claves. - Enviaste una imagen. Enviaste un sticker. - Tu invitación - %1$s creó la habitación - Tu creaste la habitación + %1$s creó la sala + Creaste la sala Invitaste a %1$s Te uniste a la Sala Dejaste la Sala @@ -167,8 +137,8 @@ Quitaste tu nombre para mostrar (era %1$s) Cambiaste el tema a: %1$s %1$s cambió el avatar de la sala - Cambiaste el avatar de la habitación - Cambiaste el nombre de la habitación a: %1$s + Cambiaste el avatar de la sala + Cambiaste el nombre de la sala a: %1$s Hiciste una videollamada. Hiciste una llamada de voz. %s envió datos para configurar la llamada. @@ -176,40 +146,35 @@ Respondiste la llamada. Terminaste la llamada. Hiciste visible el futuro historial de la %1$s - Activó el cifrado de un extremo a otro (%1$s) - Has mejorado esta habitación. - + Has activado la encriptación de Extremo-a-Extremo (%1$s) + Has actualizado esta sala. Solicitaste una conferencia de VoIP Quitaste el nombre de la sala Quitaste el tema de la sala - %1$s eliminó el avatar de la habitación - Quitaste el avatar de la habitación + %1$s eliminó el avatar de la sala + Quitaste el avatar de la sala Actualizaste tu perfil %1$s Enviaste una invitación a %1$s para unirse a la sala Revocaste la invitación para que %1$s se una a la sala Aceptaste la invitación para %1$s - %1$s agrego el widget %2$s Agregaste el widget %1$s %1$s eliminó el widget %2$s Quitaste el widget %1$s %1$s modifico el widget %2$s Modificaste el widget %1$s - Administrador Moderador Por defecto Personalizado (%1$d) Personalizado - Cambiaste el nivel de potencia de %1$s. %1$s cambió el nivel de potencia de %2$s. %1$s de %2$s a %3$s - Tu invitación. Razón: %1$s - "nvitaste a %1$s. Razón: %2$s" - Te uniste a la habitación. Razón: %1$s - Dejaste la habitación. Razón: %1$s + Invitaste a %1$s. Razón: %2$s + Te uniste a la sala. Razón: %1$s + Dejaste la sala. Razón: %1$s Rechazaste la invitación. Razón: %1$s Pateaste a %1$s. Motivo: %2$s Has desactivado a %1$s. Motivo: %2$s @@ -218,27 +183,42 @@ Revocaste la invitación para que %1$s se una a la sala. Motivo: %2$s Aceptaste la invitación para %1$s. Motivo: %2$s Retiró la invitación de %1$s\'s. Motivo: %2$s - Agregaste %1$s como dirección para esta sala. Agregaste %1$s como direcciones para esta sala. - Quitaste %1$s como dirección para esta sala. Quitaste %1$s como direcciones para esta sala. - - "%1$s agregó %2$s y eliminó %3$s como direcciones para esta sala." + %1$s añadió %2$s y eliminó %3$s como alias para esta sala. Agregaste %1$s y quitaste %2$s como direcciones para esta sala. - Estableciste la dirección principal de esta sala en %1$s. Quitaste la dirección principal de esta sala. - Ha permitido que los invitados se unan a la sala. Ha impedido que los invitados se unan a la sala. - - Activó el cifrado de extremo a extremo. - Activó el cifrado de un extremo a otro (algoritmo %1$s no reconocido). - - + Tu has activado la encriptación de Extremo-a-Extremo. + Has activado la encriptación de Extremo-a-Extremo (algoritmo %1$s no reconocido). + Has impedido que invitados se unan a la sala. + Has permitido a invitados unirse aquí. + Te has ido. Razón: %1$s + Has revocado la invitación de %1$s + Has invitado a %1$s + Has actualizado aquí. + Has hecho futuros mensajes visibles a %1$s + Te saliste de la sala + Te uniste + Creaste la conversación + %1$s ha impedido que invitados se unan a la sala. + %1$s ha permitido a invitados a unirse aquí. + %1$s se ha ido. Razón: %2$s + Tu te has unido. Razón: %1$s + %1$s se ha unido. Razón: %2$s + %1$s ha revocado la invitación de %2$s + %1$s ha invitado %2$s + %s ha actualizado aquí. + %1$s ha hecho futuros mensajes visibles a %2$s + %1$s ha salido de la sala + %1$s se ha unido + %1$s ha creado la conversación + \ No newline at end of file From c81c5c48019cc481fec9c4402fa2ec81ff2fea11 Mon Sep 17 00:00:00 2001 From: Victor Cuadrado Juan Date: Sun, 15 Nov 2020 23:10:21 +0000 Subject: [PATCH 086/231] Translated using Weblate (Spanish) Currently translated at 100.0% (4 of 4 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/es/ --- .../android/es/changelogs/40100100.txt | 1 + .../metadata/android/es/full_description.txt | 28 +++++++++---------- .../metadata/android/es/short_description.txt | 2 +- fastlane/metadata/android/es/title.txt | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 fastlane/metadata/android/es/changelogs/40100100.txt diff --git a/fastlane/metadata/android/es/changelogs/40100100.txt b/fastlane/metadata/android/es/changelogs/40100100.txt new file mode 100644 index 0000000000..70b786d12e --- /dev/null +++ b/fastlane/metadata/android/es/changelogs/40100100.txt @@ -0,0 +1 @@ +// TODO diff --git a/fastlane/metadata/android/es/full_description.txt b/fastlane/metadata/android/es/full_description.txt index 860a1f19c3..8c9915a735 100644 --- a/fastlane/metadata/android/es/full_description.txt +++ b/fastlane/metadata/android/es/full_description.txt @@ -1,30 +1,30 @@ Element es un nuevo tipo de aplicación de mensajería y colaboración que: -1. Le da el control para preservar su privacidad -2. Le permite comunicarse con cualquier persona en la red Matrix e incluso más allá al integrarse con aplicaciones como Slack. -3. Te protege de la publicidad, la minería de datos y los jardines vallados. -4. Lo protege a través del cifrado de un extremo a otro, con firma cruzada para verificar a otros +1. Te da el control para preservar su privacidad +2. Te permite comunicarse con cualquier persona en la red Matrix e incluso más allá al integrarse con aplicaciones como Slack +3. Te protege de la publicidad, la minería de datos y los jardines vallados +4. Te protege a través de encriptación de Extremo-a-Extremo, con firma cruzada para verificar a otros Element es completamente diferente de otras aplicaciones de mensajería y colaboración porque es descentralizado y de código abierto. -Element le permite autohospedarse, o elegir un host, para que tenga privacidad, propiedad y control de sus datos y conversaciones. Te da acceso a una red abierta; para que no se quede atascado hablando solo con otros usuarios de Element. Y es muy seguro. +Element te permite tener su propio servidor privado, o elegir uno público, para que tenga privacidad, posesión, y control de sus datos y conversaciones. Te da acceso a una red abierta; para que no se quede atrapado hablando solo con otros usuarios de Element. Y es muy seguro. Element puede hacer todo esto porque opera en Matrix, el estándar para la comunicación abierta y descentralizada. -Element te da el control permitiéndote elegir quién aloja tus conversaciones. Desde la aplicación Element, puede elegir hospedar de diferentes maneras: +Element te da el control permitiéndote elegir quién aloja tus conversaciones. Desde la aplicación Element, puedes elegir hospedar de diferentes maneras: -1. Obtenga una cuenta gratuita en el servidor público de matrix.org alojado por los desarrolladores de Matrix, o elija entre miles de servidores públicos alojados por voluntarios -2. Autohospede su cuenta ejecutando un servidor en su propio hardware -3. Regístrese para obtener una cuenta en un servidor personalizado simplemente suscribiéndose a la plataforma de alojamiento de Element Matrix Services +1. Obtén una cuenta gratuita en el servidor público de matrix.org alojado por los desarrolladores de Matrix, o elije entre miles de servidores públicos alojados por voluntarios +2. Autohospeda tu cuenta con un servidor en tu propio hardware +3. Regístrate para obtener una cuenta en un servidor personalizado simplemente suscribiéndote a la plataforma de alojamiento de Element Matrix Services ¿Por qué elegir Element? -POSEE SUS DATOS: Tú decides dónde guardar tus datos y mensajes. Usted es el propietario y lo controla, no algún MEGACORP que extraiga sus datos o dé acceso a terceros. +TOMA POSESIÓN DE TUS DATOS: Tú decides dónde guardar tus datos y mensajes. Tú eres el propietario y quien lo controla, no alguna MEGACORP que extrae tu datos o da acceso a terceros. -MENSAJERÍA ABIERTA Y COLABORACIÓN: Puede chatear con cualquier otra persona en la red de Matrix, ya sea que estén usando Element u otra aplicación de Matrix, e incluso si están usando un sistema de mensajería diferente como Slack, IRC o XMPP. +MENSAJERÍA ABIERTA Y COLABORACIÓN: Puede chatear con cualquier otra persona en la red de Matrix, tanto si usan Element u otra aplicación de Matrix, e incluso si están usando un sistema de mensajería diferente como Slack, IRC o XMPP. -SUPER SEGURO: Cifrado real de extremo a extremo (solo aquellos en la conversación pueden descifrar mensajes) y firma cruzada para verificar los dispositivos de los participantes de la conversación. +SUPER SEGURO: Encriptación de Extremo-a-Extremo real (solo aquellos en la conversación pueden descifrar mensajes) y firma cruzada para verificar los dispositivos de los participantes de la conversación. -COMUNICACIÓN COMPLETA: Mensajería, llamadas de voz y video, uso compartido de archivos, uso compartido de pantalla y un montón de integraciones, bots y widgets. Construya salas, comunidades, manténgase en contacto y haga las cosas. +COMUNICACIÓN COMPLETA: Mensajería, llamadas de voz y video, uso compartido de archivos, uso compartido de pantalla y un montón de integraciones, bots y widgets. Crea salas, comunidades, mantente en contacto y organízate con eficacia. -EN TODAS PARTES: Manténgase en contacto donde quiera que esté con un historial de mensajes totalmente sincronizado en todos sus dispositivos y en la web en https://app.element.io. +EN TODAS PARTES: Mantente en contacto donde quiera que estés con un historial de mensajes totalmente sincronizado en todos sus dispositivos y en la web en https://app.element.io. diff --git a/fastlane/metadata/android/es/short_description.txt b/fastlane/metadata/android/es/short_description.txt index 0562213351..473228e0df 100644 --- a/fastlane/metadata/android/es/short_description.txt +++ b/fastlane/metadata/android/es/short_description.txt @@ -1 +1 @@ -Chat y VoIP descentralizados seguros. Mantenga sus datos a salvo de terceros. +Chat y VoIP descentralizados y seguros. Mantén tus datos a salvo de terceros. diff --git a/fastlane/metadata/android/es/title.txt b/fastlane/metadata/android/es/title.txt index adc831006a..971e5cf146 100644 --- a/fastlane/metadata/android/es/title.txt +++ b/fastlane/metadata/android/es/title.txt @@ -1 +1 @@ -Element (anteriorment Riot.im) +Element (previamente Riot.im) From 9216eed1b8bf50aef853bd9436e5027a0a8a8720 Mon Sep 17 00:00:00 2001 From: gradle-update-robot Date: Tue, 17 Nov 2020 00:40:54 +0000 Subject: [PATCH 087/231] Update Gradle Wrapper from 6.7 to 6.7.1. 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 99d667ccdc..cdc95ef6eb 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=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionSha256Sum=22449f5231796abd892c98b2a07c9ceebe4688d192cd2d6763f8e3bf8acbedeb +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 82b21e6a09607eebd45afbb603a97c228fa835d6 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Tue, 17 Nov 2020 10:53:57 +0000 Subject: [PATCH 088/231] Ensure draft is saved with NonCancellable Signed-off-by: Dominic Fischer --- .../app/features/home/room/detail/RoomDetailViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ffaeb1b157..98bcdbe60e 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 @@ -50,6 +50,7 @@ import io.reactivex.functions.BiFunction import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.commonmark.parser.Parser @@ -476,7 +477,7 @@ class RoomDetailViewModel @AssistedInject constructor( * Convert a send mode to a draft and save the draft */ private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState { - viewModelScope.launch { + viewModelScope.launch(NonCancellable) { when { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { setState { copy(sendMode = it.sendMode.copy(action.draft)) } From 5bf4fffd2f4687cf6681bce845be105027cb80d2 Mon Sep 17 00:00:00 2001 From: Rintan Date: Tue, 17 Nov 2020 09:41:04 +0000 Subject: [PATCH 089/231] Translated using Weblate (Japanese) Currently translated at 50.5% (978 of 1933 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 | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index e0bfdb930d..e764f5001b 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1060,4 +1060,42 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ スキャンできません 断る 検証リクエスト + 通話の開始前に確認する + 意図しない通話を阻止する + お使いの端末は脆弱性のある古いTLSセキュリティプロトコルを使用しています、このセキュリティでは接続できません + SSLエラー + SSLエラー:相手のアイデンティティが認証されていません。 + このURLからホームサーバーに接続できませんでした、ご確認ください + 有効なMatrixサーバーアドレスではありません + このURLは検索結果に表示できません、ご確認ください + この電話番号はすでに使用されています。 + 復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。 + シングルサインオンを使用してサインインする + HDを使用する + HDを使用しない + 背面 + 前面 + カメラの切り替え + ワイヤレスヘッドセット + ヘッドセット + スピーカー + 電話 + サウンドデバイスを選択 + リアルタイム接続を確立できませんでした。 +\nホームサーバーの管理者に、通話が正常に動作するためにTURNを設定するようご連絡ください。 + 再び表示しない + アイデンティティサーバーが設定されていません。 + 終了 + 拒否 + 承諾 + 中止 + ウィジェットを削除できませんでした + ウィジェットを追加できませんでした + ビデオ通話を開始 + 会議が進行中です! + 通話を開始する権限がありません + このルームで通話を開始する権限がありません + グループ通話を開始する権限がありません + リセット + なし \ No newline at end of file From 8a44ee9d5063b82e8655af017b16811e34218971 Mon Sep 17 00:00:00 2001 From: Rintan Date: Tue, 17 Nov 2020 09:53:17 +0000 Subject: [PATCH 090/231] Translated using Weblate (Japanese) Currently translated at 42.6% (81 of 190 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/ja/ --- .../src/main/res/values-ja/strings.xml | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-ja/strings.xml b/matrix-sdk-android/src/main/res/values-ja/strings.xml index 366c743494..add19edfaf 100644 --- a/matrix-sdk-android/src/main/res/values-ja/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ja/strings.xml @@ -1,10 +1,8 @@ - + - %1$s: %2$s %1$sが画像を送信しました。 %1$sがスタンプを送信しました。 - %sの招待 %1$sが%2$sを招待しました %1$sがあなたを招待しました @@ -29,11 +27,9 @@ 部屋への招待 %1$sと%2$s 空の部屋 - %1$sと他%2$d名 - %1$sは、今後の部屋履歴を%2$sに表示させました 部屋のメンバー全員、招待された時点から。 部屋のメンバー全員、参加した時点から。 @@ -41,34 +37,50 @@ 誰でも。 不明 (%s)。 %1$s がエンドツーエンド暗号化を有効にしました (%2$s) - %1$s がVoIP会議をリクエストしました VoIP会議が開始されました VoIP会議が終了しました - (アバターも変更された) %1$s が部屋名を削除しました %1$s がルームトピックを削除しました %1$s がプロフィール %2$s を更新しました %1$s は %2$s に部屋に参加するよう招待状を送りました %1$sは%2$sの招待を受け入れました - ** 解読できません: %s ** 送信者の端末からこのメッセージのキーが送信されていません。 - 修正できませんでした メッセージを送信できません - 画像のアップロードに失敗しました - ネットワークエラー Matrixエラー - 現在空の部屋に再参加することはできません。 - 暗号化されたメッセージ - メールアドレス 電話番号 - - + ルームのアバターを変更しました + %1$sがルームのアバターを変更しました + トピックを%1$sに変更しました + 表示名を削除しました(%1$sでした) + 表示名を%1$sから%2$sに変更しました + 表示名を%1$sに設定しました + アバターを変更しました + %1$sの招待を取り下げました + %1$sをBANしました + %1$sのBANを解除しました + %1$sを退出させました + 招待を拒否しました + ルームから退出しました + %1$sがルームから退出しました + ルームから退出しました + 参加しました + %1$sが参加しました + ルームに参加しました + %1$sを招待しました + ディスカッションを作成しました + %1$sがディスカッションを作成しました + ルームを作成しました + %1$sがルームを作成しました + 招待 + ステッカーを送信しました。 + 画像を送信しました。 + \ No newline at end of file From e42cad68b4356e149735bc868c8eef89a461f880 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 18:47:38 +0000 Subject: [PATCH 091/231] Convert Group to suspend functions Signed-off-by: Dominic Fischer --- .../org/matrix/android/sdk/api/session/group/Group.kt | 6 +----- .../sdk/internal/session/group/DefaultGroup.kt | 11 ++--------- .../app/features/grouplist/GroupListViewModel.kt | 7 +++++-- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt index a4186b5a32..25c69e5025 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/group/Group.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.group -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This interface defines methods to interact within a group. */ @@ -28,8 +25,7 @@ interface Group { /** * This methods allows you to refresh data about this group. It will be reflected on the GroupSummary. * The SDK also takes care of refreshing group data every hour. - * @param callback : the matrix callback to be notified of success or failure * @return a Cancelable to be able to cancel requests. */ - fun fetchGroupData(callback: MatrixCallback): Cancelable + suspend fun fetchGroupData() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt index 01b57767b3..b47979775a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt @@ -16,20 +16,13 @@ package org.matrix.android.sdk.internal.session.group -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.group.Group -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultGroup(override val groupId: String, - private val taskExecutor: TaskExecutor, private val getGroupDataTask: GetGroupDataTask) : Group { - override fun fetchGroupData(callback: MatrixCallback): Cancelable { + override suspend fun fetchGroupData() { val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) - return getGroupDataTask.configureWith(params) { - this.callback = callback - }.executeBy(taskExecutor) + return getGroupDataTask.execute(params) } } 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 588d939635..a17aa4dbf2 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 @@ -17,6 +17,7 @@ package im.vector.app.features.grouplist +import androidx.lifecycle.viewModelScope import arrow.core.Option import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory @@ -28,7 +29,7 @@ import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import io.reactivex.Observable import io.reactivex.functions.BiFunction -import org.matrix.android.sdk.api.NoOpMatrixCallback +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams @@ -95,7 +96,9 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state -> 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 - session.getGroup(action.groupSummary.groupId)?.fetchGroupData(NoOpMatrixCallback()) + viewModelScope.launch { + session.getGroup(action.groupSummary.groupId)?.fetchGroupData() + } setState { copy(selectedGroup = action.groupSummary) } } } From 94b135ae956781fef1d74aed04220e8c90c8be00 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 18:13:13 +0000 Subject: [PATCH 092/231] Convert PushRuleService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/pushrules/PushRuleService.kt | 10 ++--- .../notification/DefaultPushRuleService.kt | 34 ++++---------- ...sAdvancedNotificationPreferenceFragment.kt | 45 +++++++++---------- ...rSettingsNotificationPreferenceFragment.kt | 34 +++++++------- .../troubleshoot/TestAccountSettings.kt | 19 ++++---- 5 files changed, 58 insertions(+), 84 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt index 880a7be9ac..4da1662681 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt @@ -15,11 +15,9 @@ */ package org.matrix.android.sdk.api.pushrules -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.RuleSet import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.util.Cancelable interface PushRuleService { /** @@ -29,13 +27,13 @@ interface PushRuleService { fun getPushRules(scope: String = RuleScope.GLOBAL): RuleSet - fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback): Cancelable + suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean) - fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback): Cancelable + suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) - fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback): Cancelable + suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) - fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback): Cancelable + suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) fun addPushRuleListener(listener: PushRuleListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index 217da269f9..f55835eb62 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.notification import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.RuleSetKey @@ -24,7 +23,6 @@ import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.RuleSet import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper import org.matrix.android.sdk.internal.database.model.PushRulesEntity import org.matrix.android.sdk.internal.database.query.where @@ -103,37 +101,21 @@ internal class DefaultPushRuleService @Inject constructor( ) } - override fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean, callback: MatrixCallback): Cancelable { + override suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean) { // The rules will be updated, and will come back from the next sync response - return updatePushRuleEnableStatusTask - .configureWith(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) { - this.callback = callback - } - .executeBy(taskExecutor) + return updatePushRuleEnableStatusTask.execute(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) } - override fun addPushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback): Cancelable { - return addPushRuleTask - .configureWith(AddPushRuleTask.Params(kind, pushRule)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) { + return addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule)) } - override fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule, callback: MatrixCallback): Cancelable { - return updatePushRuleActionsTask - .configureWith(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) { + return updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) } - override fun removePushRule(kind: RuleKind, pushRule: PushRule, callback: MatrixCallback): Cancelable { - return removePushRuleTask - .configureWith(RemovePushRuleTask.Params(kind, pushRule)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) { + return removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule)) } override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt index 67b5c03638..7e4520e4d4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -15,12 +15,13 @@ */ package im.vector.app.features.settings +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import im.vector.app.R import im.vector.app.core.preference.PushRulePreference import im.vector.app.core.preference.VectorPreference import im.vector.app.core.utils.toast -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.pushrules.rest.PushRuleAndKind import javax.inject.Inject @@ -50,29 +51,25 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() if (newRule != null) { displayLoadingView() - session.updatePushRuleActions( - ruleAndKind.kind, - preference.ruleAndKind?.pushRule ?: ruleAndKind.pushRule, - newRule, - object : MatrixCallback { - override fun onSuccess(data: Unit) { - if (!isAdded) { - return - } - preference.setPushRule(ruleAndKind.copy(pushRule = newRule)) - hideLoadingView() - } - - override fun onFailure(failure: Throwable) { - if (!isAdded) { - return - } - hideLoadingView() - // Restore the previous value - refreshDisplay() - activity?.toast(errorFormatter.toHumanReadable(failure)) - } - }) + lifecycleScope.launch { + val result = runCatching { + session.updatePushRuleActions(ruleAndKind.kind, + preference.ruleAndKind?.pushRule ?: ruleAndKind.pushRule, + newRule) + } + if (!isAdded) { + return@launch + } + result.onSuccess { + preference.setPushRule(ruleAndKind.copy(pushRule = newRule)) + } + hideLoadingView() + result.onFailure { failure -> + // Restore the previous value + refreshDisplay() + activity?.toast(errorFormatter.toHumanReadable(failure)) + } + } } false } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 4bee1ac0c8..47868eed51 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -23,6 +23,7 @@ import android.media.RingtoneManager import android.net.Uri import android.os.Parcelable import android.widget.Toast +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.SwitchPreference import im.vector.app.R @@ -37,6 +38,7 @@ import im.vector.app.core.utils.isIgnoringBatteryOptimizations import im.vector.app.core.utils.requestDisablingBatteryOptimization import im.vector.app.features.notifications.NotificationUtils import im.vector.app.push.fcm.FcmHelper +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.pushrules.RuleIds @@ -318,24 +320,22 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( .find { it.ruleId == RuleIds.RULE_ID_DISABLE_ALL } ?.let { // Trick, we must enable this room to disable notifications - pushRuleService.updatePushRuleEnableStatus(RuleKind.OVERRIDE, - it, - !switchPref.isChecked, - object : MatrixCallback { - override fun onSuccess(data: Unit) { - // Push rules will be updated from the sync - } + lifecycleScope.launch { + try { + pushRuleService.updatePushRuleEnableStatus(RuleKind.OVERRIDE, + it, + !switchPref.isChecked) + // Push rules will be updated from the sync + } catch (failure: Throwable) { + if (!isAdded) { + return@launch + } - override fun onFailure(failure: Throwable) { - if (!isAdded) { - return - } - - // revert the check box - switchPref.isChecked = !switchPref.isChecked - Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() - } - }) + // revert the check box + switchPref.isChecked = !switchPref.isChecked + Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() + } + } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt index 0c3390d0b0..b78dba07f5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt @@ -20,7 +20,8 @@ import androidx.activity.result.ActivityResultLauncher import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.pushrules.RuleKind import javax.inject.Inject @@ -48,16 +49,12 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String override fun doFix() { if (manager?.diagStatus == TestStatus.RUNNING) return // wait before all is finished - session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled, - object : MatrixCallback { - override fun onSuccess(data: Unit) { - manager?.retry(activityResultLauncher) - } - - override fun onFailure(failure: Throwable) { - manager?.retry(activityResultLauncher) - } - }) + GlobalScope.launch { + runCatching { + session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) + } + manager?.retry(activityResultLauncher) + } } } status = TestStatus.FAILED From a32d7f78bb8798154c5054d8e7032d8242b2c1be Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 19:20:01 +0000 Subject: [PATCH 093/231] Convert SearchService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/session/search/SearchMessagesTest.kt | 53 ++++++++----------- .../sdk/api/session/search/SearchService.kt | 21 +++----- .../session/search/DefaultSearchService.kt | 45 +++++++--------- 3 files changed, 47 insertions(+), 72 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index 7db159cd0b..ae300c936d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -71,38 +71,27 @@ class SearchMessagesTest : InstrumentedTest { commonTestHelper.await(lock) lock = CountDownLatch(1) - aliceSession - .searchService() - .search( - searchTerm = "lore", - limit = 10, - includeProfile = true, - afterLimit = 0, - beforeLimit = 10, - orderByRecent = true, - nextBatch = null, - roomId = aliceRoomId, - callback = object : MatrixCallback { - override fun onSuccess(data: SearchResult) { - super.onSuccess(data) - assertTrue(data.results?.size == 2) - assertTrue( - data.results - ?.all { - (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() - }.orFalse() - ) - lock.countDown() - } - - override fun onFailure(failure: Throwable) { - super.onFailure(failure) - fail(failure.localizedMessage) - lock.countDown() - } - } - ) - lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + val data = commonTestHelper.runBlockingTest { + aliceSession + .searchService() + .search( + searchTerm = "lore", + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null, + roomId = aliceRoomId + ) + } + assertTrue(data.results?.size == 2) + assertTrue( + data.results + ?.all { + (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() + }.orFalse() + ) aliceTimeline.removeAllListeners() cryptoTestData.cleanUp(commonTestHelper) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt index ef2eec433f..bc1c9e5769 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/search/SearchService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.search -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This interface defines methods to search messages in rooms. */ @@ -35,15 +32,13 @@ interface SearchService { * @param beforeLimit how many events before the result are returned. * @param afterLimit how many events after the result are returned. * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. - * @param callback Callback to get the search result */ - fun search(searchTerm: String, - roomId: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean, - callback: MatrixCallback): Cancelable + suspend fun search(searchTerm: String, + roomId: String, + nextBatch: String?, + orderByRecent: Boolean, + limit: Int, + beforeLimit: Int, + afterLimit: Int, + includeProfile: Boolean): SearchResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt index 2ba1eebe61..8033b0654d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/DefaultSearchService.kt @@ -16,40 +16,31 @@ package org.matrix.android.sdk.internal.session.search -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.search.SearchService -import org.matrix.android.sdk.api.util.Cancelable import javax.inject.Inject -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultSearchService @Inject constructor( - private val taskExecutor: TaskExecutor, private val searchTask: SearchTask ) : SearchService { - override fun search(searchTerm: String, - roomId: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean, - callback: MatrixCallback): Cancelable { - return searchTask - .configureWith(SearchTask.Params( - searchTerm = searchTerm, - roomId = roomId, - nextBatch = nextBatch, - orderByRecent = orderByRecent, - limit = limit, - beforeLimit = beforeLimit, - afterLimit = afterLimit, - includeProfile = includeProfile - )) { - this.callback = callback - }.executeBy(taskExecutor) + override suspend fun search(searchTerm: String, + roomId: String, + nextBatch: String?, + orderByRecent: Boolean, + limit: Int, + beforeLimit: Int, + afterLimit: Int, + includeProfile: Boolean): SearchResult { + return searchTask.execute(SearchTask.Params( + searchTerm = searchTerm, + roomId = roomId, + nextBatch = nextBatch, + orderByRecent = orderByRecent, + limit = limit, + beforeLimit = beforeLimit, + afterLimit = afterLimit, + includeProfile = includeProfile + )) } } From af8b400bf4c61a9799d394e814d17c4e7b9ea16f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 13:44:55 +0100 Subject: [PATCH 094/231] Room creation form: input an alias for public room (#1314) --- .../session/room/failure/CreateRoomFailure.kt | 3 + .../session/room/create/CreateRoomTask.kt | 46 ++++++++-- .../app/features/form/FormSwitchItem.kt | 7 ++ .../createroom/CreateRoomAction.kt | 2 +- .../createroom/CreateRoomController.kt | 36 +++++--- .../createroom/CreateRoomFragment.kt | 5 +- .../createroom/CreateRoomViewModel.kt | 69 +++++++++----- .../createroom/CreateRoomViewState.kt | 8 +- .../createroom/RoomAliasEditItem.kt | 89 +++++++++++++++++++ .../res/layout/item_room_alias_text_input.xml | 63 +++++++++++++ vector/src/main/res/values/strings.xml | 5 ++ 11 files changed, 290 insertions(+), 43 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt create mode 100644 vector/src/main/res/layout/item_room_alias_text_input.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 b0df2963f7..88ab5e36c6 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 @@ -22,4 +22,7 @@ import org.matrix.android.sdk.api.failure.MatrixError sealed class CreateRoomFailure : Failure.FeatureFailure() { object CreatedWithTimeout : CreateRoomFailure() data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() + object RoomAliasEmpty: CreateRoomFailure() + object RoomAliasNotAvailable: CreateRoomFailure() + object RoomAliasInvalid: CreateRoomFailure() } 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 4f0aaf083d..8bf1f077b1 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 @@ -31,8 +31,10 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask @@ -45,6 +47,7 @@ internal interface CreateRoomTask : Task internal class DefaultCreateRoomTask @Inject constructor( private val roomAPI: RoomAPI, + @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, @@ -61,6 +64,31 @@ internal class DefaultCreateRoomTask @Inject constructor( ?: throw IllegalStateException("You can't create a direct room without an invitedUser") } else null + if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { + if (params.roomAliasName.isNullOrEmpty()) { + throw CreateRoomFailure.RoomAliasEmpty + } + // Check alias availability + val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":") + try { + executeRequest(eventBus) { + apiCall = roomAPI.getRoomIdByAlias(fullAlias) + } + } catch (throwable: Throwable) { + if (throwable is Failure.ServerError && throwable.httpCode == 404) { + // This is a 404, so the alias is available: nominal case + null + } else { + // Other error, propagate it + throw throwable + } + } + ?.let { + // Alias already exists: error case + throw CreateRoomFailure.RoomAliasNotAvailable + } + } + val createRoomBody = createRoomBodyBuilder.build(params) val createRoomResponse = try { @@ -68,14 +96,18 @@ internal class DefaultCreateRoomTask @Inject constructor( apiCall = roomAPI.createRoom(createRoomBody) } } catch (throwable: Throwable) { - if (throwable is Failure.ServerError - && throwable.httpCode == 403 - && throwable.error.code == MatrixError.M_FORBIDDEN - && throwable.error.message.startsWith("Federation denied with")) { - throw CreateRoomFailure.CreatedWithFederationFailure(throwable.error) - } else { - throw throwable + if (throwable is Failure.ServerError) { + if (throwable.httpCode == 403 + && throwable.error.code == MatrixError.M_FORBIDDEN + && throwable.error.message.startsWith("Federation denied with")) { + throw CreateRoomFailure.CreatedWithFederationFailure(throwable.error) + } else if (throwable.httpCode == 400 + && throwable.error.code == MatrixError.M_UNKNOWN + && throwable.error.message == "Invalid characters in room alias") { + throw CreateRoomFailure.RoomAliasInvalid + } } + throw throwable } val roomId = createRoomResponse.roomId // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) diff --git a/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt b/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt index 08d1332920..0b274cccd8 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt @@ -16,7 +16,9 @@ package im.vector.app.features.form +import android.view.View import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import com.google.android.material.switchmaterial.SwitchMaterial @@ -43,6 +45,9 @@ abstract class FormSwitchItem : VectorEpoxyModel() { @EpoxyAttribute var summary: String? = null + @EpoxyAttribute + var showDivider: Boolean = true + override fun bind(holder: Holder) { super.bind(holder) holder.view.setOnClickListener { @@ -61,6 +66,7 @@ abstract class FormSwitchItem : VectorEpoxyModel() { holder.switchView.setOnCheckedChangeListener { _, isChecked -> listener?.invoke(isChecked) } + holder.divider.isVisible = showDivider } override fun shouldSaveViewState(): Boolean { @@ -77,5 +83,6 @@ abstract class FormSwitchItem : VectorEpoxyModel() { val titleView by bind(R.id.formSwitchTitle) val summaryView by bind(R.id.formSwitchSummary) val switchView by bind(R.id.formSwitchSwitch) + val divider by bind(R.id.formSwitchDivider) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt index 61799a741f..b50c56e1db 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomAction.kt @@ -24,7 +24,7 @@ sealed class CreateRoomAction : VectorViewModelAction { data class SetName(val name: String) : CreateRoomAction() data class SetTopic(val topic: String) : CreateRoomAction() data class SetIsPublic(val isPublic: Boolean) : CreateRoomAction() - data class SetIsInRoomDirectory(val isInRoomDirectory: Boolean) : CreateRoomAction() + data class SetRoomAliasLocalPart(val aliasLocalPart: String) : CreateRoomAction() data class SetIsEncrypted(val isEncrypted: Boolean) : CreateRoomAction() object ToggleShowAdvanced : CreateRoomAction() 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 157e192668..3f86d1adca 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 @@ -32,6 +32,7 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.form.formSubmitButtonItem import im.vector.app.features.form.formSwitchItem +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import javax.inject.Inject class CreateRoomController @Inject constructor(private val stringProvider: StringProvider, @@ -61,6 +62,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin is Fail -> { // display the form buildForm(viewState, true) + // TODO BMA DO NOT COMMIT Update this errorWithRetryItem { id("error") text(errorFormatter.toHumanReadable(asyncCreateRoom.error)) @@ -115,21 +117,32 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin enabled(enableFormElement) title(stringProvider.getString(R.string.create_room_public_title)) summary(stringProvider.getString(R.string.create_room_public_description)) - switchChecked(viewState.isPublic) + switchChecked(viewState.roomType is CreateRoomViewState.RoomType.Public) + showDivider(viewState.roomType !is CreateRoomViewState.RoomType.Public) listener { value -> listener?.setIsPublic(value) } } - formSwitchItem { - id("directory") - enabled(enableFormElement) - title(stringProvider.getString(R.string.create_room_directory_title)) - summary(stringProvider.getString(R.string.create_room_directory_description)) - switchChecked(viewState.isInRoomDirectory) - - listener { value -> - listener?.setIsInRoomDirectory(value) + // Room alias + if (viewState.roomType is CreateRoomViewState.RoomType.Public) { + roomAliasEditItem { + id("alias") + enabled(enableFormElement) + value(viewState.roomType.aliasLocalPart) + homeServer(":" + viewState.homeServerName) + errorMessage( + when ((viewState.asyncCreateRoomRequest as? Fail)?.error) { + is CreateRoomFailure.RoomAliasEmpty -> R.string.create_room_alias_empty + is CreateRoomFailure.RoomAliasNotAvailable -> R.string.create_room_alias_already_in_use + is CreateRoomFailure.RoomAliasInvalid -> R.string.create_room_alias_invalid + else -> null + } + ?.let { stringProvider.getString(it) } + ) + onTextChange { value -> + listener?.setAliasLocalPart(value) + } } } formSwitchItem { @@ -162,6 +175,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin title(stringProvider.getString(R.string.create_room_disable_federation_title, viewState.homeServerName)) summary(stringProvider.getString(R.string.create_room_disable_federation_description)) switchChecked(viewState.disableFederation) + showDivider(false) listener { value -> listener?.setDisableFederation(value) } } } @@ -179,7 +193,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin fun onNameChange(newName: String) fun onTopicChange(newTopic: String) fun setIsPublic(isPublic: Boolean) - fun setIsInRoomDirectory(isInRoomDirectory: Boolean) + fun setAliasLocalPart(aliasLocalPart: String) fun setIsEncrypted(isEncrypted: Boolean) fun retry() fun toggleShowAdvanced() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 729c97370c..124b6a415a 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -46,6 +46,7 @@ class CreateRoomFragment @Inject constructor( OnBackPressed { private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel + // TODO BMA: use fragmentViewMode(). Else back does not reset the form. Use Fragment Argument to pass room name private val viewModel: CreateRoomViewModel by activityViewModel() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) @@ -102,8 +103,8 @@ class CreateRoomFragment @Inject constructor( viewModel.handle(CreateRoomAction.SetIsPublic(isPublic)) } - override fun setIsInRoomDirectory(isInRoomDirectory: Boolean) { - viewModel.handle(CreateRoomAction.SetIsInRoomDirectory(isInRoomDirectory)) + override fun setAliasLocalPart(aliasLocalPart: String) { + viewModel.handle(CreateRoomAction.SetRoomAliasLocalPart(aliasLocalPart)) } override fun setIsEncrypted(isEncrypted: Boolean) { 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 fcb98916cc..52e1b3f801 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 @@ -77,7 +77,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr setState { copy( - isEncrypted = !isPublic && adminE2EByDefault, + isEncrypted = roomType is CreateRoomViewState.RoomType.Private && adminE2EByDefault, hsAdminHasDisabledE2E = !adminE2EByDefault ) } @@ -100,16 +100,16 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr override fun handle(action: CreateRoomAction) { when (action) { - is CreateRoomAction.SetAvatar -> setAvatar(action) - is CreateRoomAction.SetName -> setName(action) - is CreateRoomAction.SetTopic -> setTopic(action) - is CreateRoomAction.SetIsPublic -> setIsPublic(action) - is CreateRoomAction.SetIsInRoomDirectory -> setIsInRoomDirectory(action) - is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action) - is CreateRoomAction.Create -> doCreateRoom() - CreateRoomAction.Reset -> doReset() - CreateRoomAction.ToggleShowAdvanced -> toggleShowAdvanced() - is CreateRoomAction.DisableFederation -> disableFederation(action) + is CreateRoomAction.SetAvatar -> setAvatar(action) + is CreateRoomAction.SetName -> setName(action) + is CreateRoomAction.SetTopic -> setTopic(action) + is CreateRoomAction.SetIsPublic -> setIsPublic(action) + is CreateRoomAction.SetRoomAliasLocalPart -> setRoomAliasLocalPart(action) + is CreateRoomAction.SetIsEncrypted -> setIsEncrypted(action) + is CreateRoomAction.Create -> doCreateRoom() + CreateRoomAction.Reset -> doReset() + CreateRoomAction.ToggleShowAdvanced -> toggleShowAdvanced() + is CreateRoomAction.DisableFederation -> disableFederation(action) }.exhaustive } @@ -150,13 +150,31 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr private fun setTopic(action: CreateRoomAction.SetTopic) = setState { copy(roomTopic = action.topic) } private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState { - copy( - isPublic = action.isPublic, - isEncrypted = !action.isPublic && adminE2EByDefault - ) + if (action.isPublic) { + copy( + roomType = CreateRoomViewState.RoomType.Public(""), + isEncrypted = false + ) + } else { + copy( + roomType = CreateRoomViewState.RoomType.Private, + isEncrypted = adminE2EByDefault + ) + } } - private fun setIsInRoomDirectory(action: CreateRoomAction.SetIsInRoomDirectory) = setState { copy(isInRoomDirectory = action.isInRoomDirectory) } + private fun setRoomAliasLocalPart(action: CreateRoomAction.SetRoomAliasLocalPart) { + withState { state -> + if (state.roomType is CreateRoomViewState.RoomType.Public) { + setState { + copy( + roomType = CreateRoomViewState.RoomType.Public(action.aliasLocalPart) + ) + } + } + } + // Else ignore + } private fun setIsEncrypted(action: CreateRoomAction.SetIsEncrypted) = setState { copy(isEncrypted = action.isEncrypted) } @@ -174,10 +192,21 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr name = state.roomName.takeIf { it.isNotBlank() } topic = state.roomTopic.takeIf { it.isNotBlank() } avatarUri = state.avatarUri - // Directory visibility - visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE - // Public room - preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + when (state.roomType) { + is CreateRoomViewState.RoomType.Public -> { + // Directory visibility + visibility = RoomDirectoryVisibility.PUBLIC + // Preset + preset = CreateRoomPreset.PRESET_PUBLIC_CHAT + roomAliasName = state.roomType.aliasLocalPart + } + is CreateRoomViewState.RoomType.Private -> { + // Directory visibility + visibility = RoomDirectoryVisibility.PRIVATE + // Preset + preset = CreateRoomPreset.PRESET_PRIVATE_CHAT + } + }.exhaustive // Disabling federation disableFederation = state.disableFederation 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 d1e5c0b1bd..cc7a441eb5 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 @@ -25,8 +25,7 @@ data class CreateRoomViewState( val avatarUri: Uri? = null, val roomName: String = "", val roomTopic: String = "", - val isPublic: Boolean = false, - val isInRoomDirectory: Boolean = false, + val roomType: RoomType = RoomType.Private, val isEncrypted: Boolean = false, val showAdvanced: Boolean = false, val disableFederation: Boolean = false, @@ -39,4 +38,9 @@ data class CreateRoomViewState( * Return true if there is not important input from user */ fun isEmpty() = avatarUri == null && roomName.isEmpty() && roomTopic.isEmpty() + + sealed class RoomType { + object Private : RoomType() + data class Public(val aliasLocalPart: String) : RoomType() + } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt new file mode 100644 index 0000000000..041a5c5c51 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasEditItem.kt @@ -0,0 +1,89 @@ +/* + * 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.roomdirectory.createroom + +import android.text.Editable +import android.view.View +import android.widget.TextView +import androidx.core.view.isVisible +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.platform.SimpleTextWatcher + +@EpoxyModelClass(layout = R.layout.item_room_alias_text_input) +abstract class RoomAliasEditItem : VectorEpoxyModel() { + + @EpoxyAttribute + var value: String? = null + + @EpoxyAttribute + var showBottomSeparator: Boolean = true + + @EpoxyAttribute + var errorMessage: String? = null + + @EpoxyAttribute + var homeServer: String? = null + + @EpoxyAttribute + var enabled: Boolean = true + + @EpoxyAttribute + var onTextChange: ((String) -> Unit)? = null + + private val onTextChangeListener = object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + onTextChange?.invoke(s.toString()) + } + } + + override fun bind(holder: Holder) { + super.bind(holder) + holder.textInputLayout.isEnabled = enabled + holder.textInputLayout.error = errorMessage + + // Update only if text is different and value is not null + if (value != null && holder.textInputEditText.text.toString() != value) { + holder.textInputEditText.setText(value) + } + holder.textInputEditText.isEnabled = enabled + holder.textInputEditText.addTextChangedListener(onTextChangeListener) + holder.homeServerText.text = homeServer + holder.bottomSeparator.isVisible = showBottomSeparator + } + + override fun shouldSaveViewState(): Boolean { + return false + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + holder.textInputEditText.removeTextChangedListener(onTextChangeListener) + } + + class Holder : VectorEpoxyHolder() { + val textInputLayout by bind(R.id.itemRoomAliasTextInputLayout) + val textInputEditText by bind(R.id.itemRoomAliasTextInputEditText) + val homeServerText by bind(R.id.itemRoomAliasHomeServer) + val bottomSeparator by bind(R.id.itemRoomAliasDivider) + } +} diff --git a/vector/src/main/res/layout/item_room_alias_text_input.xml b/vector/src/main/res/layout/item_room_alias_text_input.xml new file mode 100644 index 0000000000..44c76b631d --- /dev/null +++ b/vector/src/main/res/layout/item_room_alias_text_input.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 5768750021..ee8c3f7da8 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2100,6 +2100,11 @@ Block anyone not part of %s from ever joining this room You might enable this if the room will only be used for collaborating with internal teams on your homeserver. This cannot be changed later. + Room address + This address is already in use + Please provide a room address + Some characters are not allowed + Your email domain is not authorized to register on this server Untrusted sign in From 16b6678aa2df443efe95aeaddb572893a99567c9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 14:14:43 +0100 Subject: [PATCH 095/231] CreateRoomFragment: use argument instead of Activity ViewModel to pass the initial room name --- .../roomdirectory/RoomDirectoryActivity.kt | 14 +++++++------- .../createroom/CreateRoomActivity.kt | 12 +++++------- .../createroom/CreateRoomFragment.kt | 15 ++++++++++++--- .../createroom/CreateRoomViewModel.kt | 12 +++--------- .../createroom/CreateRoomViewState.kt | 4 ++++ 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt index 9dc41cbc21..a21f4e670a 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryActivity.kt @@ -26,18 +26,15 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.features.roomdirectory.createroom.CreateRoomAction import im.vector.app.features.roomdirectory.createroom.CreateRoomFragment -import im.vector.app.features.roomdirectory.createroom.CreateRoomViewModel +import im.vector.app.features.roomdirectory.createroom.CreateRoomArgs import im.vector.app.features.roomdirectory.picker.RoomDirectoryPickerFragment import javax.inject.Inject class RoomDirectoryActivity : VectorBaseActivity() { - @Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory @Inject lateinit var roomDirectoryViewModelFactory: RoomDirectoryViewModel.Factory private val roomDirectoryViewModel: RoomDirectoryViewModel by viewModel() - private val createRoomViewModel: CreateRoomViewModel by viewModel() private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel override fun getLayoutRes() = R.layout.activity_simple @@ -60,10 +57,13 @@ class RoomDirectoryActivity : VectorBaseActivity() { when (sharedAction) { is RoomDirectorySharedAction.Back -> onBackPressed() is RoomDirectorySharedAction.CreateRoom -> { - addFragmentToBackstack(R.id.simpleFragmentContainer, CreateRoomFragment::class.java) - // Transmit the filter to the createRoomViewModel + // Transmit the filter to the CreateRoomFragment withState(roomDirectoryViewModel) { - createRoomViewModel.handle(CreateRoomAction.SetName(it.currentFilter)) + addFragmentToBackstack( + R.id.simpleFragmentContainer, + CreateRoomFragment::class.java, + CreateRoomArgs(it.currentFilter) + ) } } is RoomDirectorySharedAction.ChangeProtocol -> diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt index e003c4905c..b6ee00a52f 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.appcompat.widget.Toolbar -import com.airbnb.mvrx.viewModel import im.vector.app.R import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.addFragment @@ -28,16 +27,12 @@ import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel -import javax.inject.Inject /** * Simple container for [CreateRoomFragment] */ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { - @Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory - private val createRoomViewModel: CreateRoomViewModel by viewModel() - private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel override fun getLayoutRes() = R.layout.activity_simple @@ -52,8 +47,11 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { override fun initUiAndData() { if (isFirstCreation()) { - addFragment(R.id.simpleFragmentContainer, CreateRoomFragment::class.java) - createRoomViewModel.handle(CreateRoomAction.SetName(intent?.getStringExtra(INITIAL_NAME) ?: "")) + addFragment( + R.id.simpleFragmentContainer, + CreateRoomFragment::class.java, + CreateRoomArgs(intent?.getStringExtra(INITIAL_NAME) ?: "") + ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index 124b6a415a..a20bc7dbf5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -18,10 +18,12 @@ package im.vector.app.features.roomdirectory.createroom import android.net.Uri import android.os.Bundle +import android.os.Parcelable import android.view.View import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.Success -import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper @@ -33,12 +35,19 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_create_room.* import timber.log.Timber import javax.inject.Inject +@Parcelize +data class CreateRoomArgs( + val initialName: String +) : Parcelable + class CreateRoomFragment @Inject constructor( private val createRoomController: CreateRoomController, + val createRoomViewModelFactory: CreateRoomViewModel.Factory, colorProvider: ColorProvider ) : VectorBaseFragment(), CreateRoomController.Listener, @@ -46,8 +55,8 @@ class CreateRoomFragment @Inject constructor( OnBackPressed { private lateinit var sharedActionViewModel: RoomDirectorySharedActionViewModel - // TODO BMA: use fragmentViewMode(). Else back does not reset the form. Use Fragment Argument to pass room name - private val viewModel: CreateRoomViewModel by activityViewModel() + private val viewModel: CreateRoomViewModel by fragmentViewModel() + private val args: CreateRoomArgs by args() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) 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 52e1b3f801..e25298b0f1 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 @@ -17,10 +17,9 @@ package im.vector.app.features.roomdirectory.createroom import androidx.core.net.toFile -import androidx.fragment.app.FragmentActivity 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 @@ -31,7 +30,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault -import im.vector.app.features.roomdirectory.RoomDirectoryActivity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback @@ -88,13 +86,9 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreateRoomViewState): CreateRoomViewModel? { - val activity: FragmentActivity = (viewModelContext as ActivityViewModelContext).activity() + val fragment: CreateRoomFragment = (viewModelContext as FragmentViewModelContext).fragment() - return when (activity) { - is CreateRoomActivity -> activity.createRoomViewModelFactory.create(state) - is RoomDirectoryActivity -> activity.createRoomViewModelFactory.create(state) - else -> error("Wrong activity") - } + return fragment.createRoomViewModelFactory.create(state) } } 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 cc7a441eb5..398f804db3 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 @@ -34,6 +34,10 @@ data class CreateRoomViewState( val asyncCreateRoomRequest: Async = Uninitialized ) : MvRxState { + constructor(args: CreateRoomArgs) : this( + roomName = args.initialName + ) + /** * Return true if there is not important input from user */ From 507d5d37588611ccf6690ff616bcf5024a417ee1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 14:21:53 +0100 Subject: [PATCH 096/231] Warn the user if room alias is not empty and he wants to leave the form. --- .../roomdirectory/createroom/CreateRoomViewState.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 398f804db3..4609693c8f 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 @@ -20,6 +20,7 @@ import android.net.Uri import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.api.extensions.orTrue data class CreateRoomViewState( val avatarUri: Uri? = null, @@ -41,7 +42,10 @@ data class CreateRoomViewState( /** * Return true if there is not important input from user */ - fun isEmpty() = avatarUri == null && roomName.isEmpty() && roomTopic.isEmpty() + fun isEmpty() = avatarUri == null + && roomName.isEmpty() + && roomTopic.isEmpty() + && (roomType as? RoomType.Public)?.aliasLocalPart?.isEmpty().orTrue() sealed class RoomType { object Private : RoomType() From 514263ae122a67a5ce7d6078a8d816db7eac3320 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 14:51:10 +0100 Subject: [PATCH 097/231] Room creation Fragment: no more "Retry" action, loading and error displayed in a dialog --- .../session/room/failure/CreateRoomFailure.kt | 8 ++-- .../session/room/create/CreateRoomTask.kt | 6 +-- .../createroom/CreateRoomController.kt | 37 +++---------------- .../createroom/CreateRoomFragment.kt | 27 ++++++++++---- .../createroom/CreateRoomViewEvents.kt | 1 + .../createroom/CreateRoomViewModel.kt | 2 + .../main/res/layout/fragment_create_room.xml | 2 + vector/src/main/res/values/strings.xml | 1 + 8 files changed, 40 insertions(+), 44 deletions(-) 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 88ab5e36c6..b4e2dc645c 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 @@ -22,7 +22,9 @@ import org.matrix.android.sdk.api.failure.MatrixError sealed class CreateRoomFailure : Failure.FeatureFailure() { object CreatedWithTimeout : CreateRoomFailure() data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() - object RoomAliasEmpty: CreateRoomFailure() - object RoomAliasNotAvailable: CreateRoomFailure() - object RoomAliasInvalid: CreateRoomFailure() + sealed class RoomAliasError : CreateRoomFailure() { + object AliasEmpty : RoomAliasError() + object AliasNotAvailable : RoomAliasError() + object AliasInvalid : RoomAliasError() + } } 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 8bf1f077b1..0fe9b0ba68 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 @@ -66,7 +66,7 @@ internal class DefaultCreateRoomTask @Inject constructor( if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { if (params.roomAliasName.isNullOrEmpty()) { - throw CreateRoomFailure.RoomAliasEmpty + throw CreateRoomFailure.RoomAliasError.AliasEmpty } // Check alias availability val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":") @@ -85,7 +85,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } ?.let { // Alias already exists: error case - throw CreateRoomFailure.RoomAliasNotAvailable + throw CreateRoomFailure.RoomAliasError.AliasNotAvailable } } @@ -104,7 +104,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } else if (throwable.httpCode == 400 && throwable.error.code == MatrixError.M_UNKNOWN && throwable.error.message == "Invalid characters in room alias") { - throw CreateRoomFailure.RoomAliasInvalid + throw CreateRoomFailure.RoomAliasError.AliasInvalid } } throw throwable 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 3f86d1adca..7acc2d1490 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 @@ -44,32 +44,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin var index = 0 override fun buildModels(viewState: CreateRoomViewState) { - when (val asyncCreateRoom = viewState.asyncCreateRoomRequest) { - is Success -> { - // Nothing to display, the screen will be closed - } - is Loading -> { - // display the form - buildForm(viewState, false) - loadingItem { - id("loading") - } - } - is Uninitialized -> { - // display the form - buildForm(viewState, true) - } - is Fail -> { - // display the form - buildForm(viewState, true) - // TODO BMA DO NOT COMMIT Update this - errorWithRetryItem { - id("error") - text(errorFormatter.toHumanReadable(asyncCreateRoom.error)) - listener { listener?.retry() } - } - } - } + // display the form + buildForm(viewState, viewState.asyncCreateRoomRequest !is Loading) } private fun buildForm(viewState: CreateRoomViewState, enableFormElement: Boolean) { @@ -133,10 +109,10 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin homeServer(":" + viewState.homeServerName) errorMessage( when ((viewState.asyncCreateRoomRequest as? Fail)?.error) { - is CreateRoomFailure.RoomAliasEmpty -> R.string.create_room_alias_empty - is CreateRoomFailure.RoomAliasNotAvailable -> R.string.create_room_alias_already_in_use - is CreateRoomFailure.RoomAliasInvalid -> R.string.create_room_alias_invalid - else -> null + is CreateRoomFailure.RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty + is CreateRoomFailure.RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use + is CreateRoomFailure.RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid + else -> null } ?.let { stringProvider.getString(it) } ) @@ -195,7 +171,6 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin fun setIsPublic(isPublic: Boolean) fun setAliasLocalPart(aliasLocalPart: String) fun setIsEncrypted(isEncrypted: Boolean) - fun retry() fun toggleShowAdvanced() fun setDisableFederation(disableFederation: Boolean) fun submit() diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index a20bc7dbf5..fb90752764 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -21,6 +21,8 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel @@ -37,7 +39,8 @@ import im.vector.app.features.roomdirectory.RoomDirectorySharedAction import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_create_room.* -import timber.log.Timber +import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import javax.inject.Inject @Parcelize @@ -66,17 +69,31 @@ class CreateRoomFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) vectorBaseActivity.setSupportActionBar(createRoomToolbar) sharedActionViewModel = activityViewModelProvider.get(RoomDirectorySharedActionViewModel::class.java) + setupWaitingView() setupRecyclerView() createRoomClose.debouncedClicks { sharedActionViewModel.post(RoomDirectorySharedAction.Back) } viewModel.observeViewEvents { when (it) { - CreateRoomViewEvents.Quit -> vectorBaseActivity.onBackPressed() + CreateRoomViewEvents.Quit -> vectorBaseActivity.onBackPressed() + is CreateRoomViewEvents.Failure -> showFailure(it.throwable) }.exhaustive } } + override fun showFailure(throwable: Throwable) { + // Note: RoomAliasError are displayed directly in the form + if (throwable !is CreateRoomFailure.RoomAliasError) { + super.showFailure(throwable) + } + } + + private fun setupWaitingView() { + waiting_view_status_text.isVisible = true + waiting_view_status_text.setText(R.string.create_room_in_progress) + } + override fun onDestroyView() { createRoomForm.cleanup() createRoomController.listener = null @@ -132,11 +149,6 @@ class CreateRoomFragment @Inject constructor( viewModel.handle(CreateRoomAction.Create) } - override fun retry() { - Timber.v("Retry") - viewModel.handle(CreateRoomAction.Create) - } - override fun onBackPressed(toolbarButton: Boolean): Boolean { return withState(viewModel) { return@withState if (!it.isEmpty()) { @@ -157,6 +169,7 @@ class CreateRoomFragment @Inject constructor( override fun invalidate() = withState(viewModel) { state -> val async = state.asyncCreateRoomRequest + waiting_view.isVisible = async is Loading if (async is Success) { // Navigate to freshly created room navigator.openRoom(requireActivity(), async()) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt index 4ff4ee4bdf..af745ce5ff 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewEvents.kt @@ -22,5 +22,6 @@ import im.vector.app.core.platform.VectorViewEvents * Transient events for room creation screen */ sealed class CreateRoomViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : CreateRoomViewEvents() object Quit : CreateRoomViewEvents() } 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 e25298b0f1..4cffbf68dd 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 @@ -36,6 +36,7 @@ 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 +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 @@ -221,6 +222,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr setState { copy(asyncCreateRoomRequest = Fail(failure)) } + _viewEvents.post(CreateRoomViewEvents.Failure(failure)) } }) } diff --git a/vector/src/main/res/layout/fragment_create_room.xml b/vector/src/main/res/layout/fragment_create_room.xml index 0fe5caa968..3abcafc94d 100644 --- a/vector/src/main/res/layout/fragment_create_room.xml +++ b/vector/src/main/res/layout/fragment_create_room.xml @@ -67,5 +67,7 @@ + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ee8c3f7da8..f00e51710d 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2104,6 +2104,7 @@ This address is already in use Please provide a room address Some characters are not allowed + Creating room… Your email domain is not authorized to register on this server From f6aee3d64e372c751a0ca16291890924275f758b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 14:54:01 +0100 Subject: [PATCH 098/231] Cleanup --- .../features/roomdirectory/createroom/CreateRoomController.kt | 4 ---- .../features/roomdirectory/createroom/CreateRoomViewModel.kt | 1 - 2 files changed, 5 deletions(-) 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 7acc2d1490..740e73b955 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 @@ -19,11 +19,7 @@ package im.vector.app.features.roomdirectory.createroom import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized import im.vector.app.R -import im.vector.app.core.epoxy.errorWithRetryItem -import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.discovery.settingsSectionTitleItem 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 4cffbf68dd..8478b0f3b8 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 @@ -36,7 +36,6 @@ 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 -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 From 0c7f4c2af572188f7f9ddcbc050c8dea178a8d7d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 15:13:43 +0100 Subject: [PATCH 099/231] Reset error in the form --- .../roomdirectory/createroom/CreateRoomViewModel.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 8478b0f3b8..216a016fbe 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 @@ -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 @@ -147,6 +148,8 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr if (action.isPublic) { copy( roomType = CreateRoomViewState.RoomType.Public(""), + // Reset any error in the form about alias + asyncCreateRoomRequest = Uninitialized, isEncrypted = false ) } else { @@ -162,7 +165,9 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr if (state.roomType is CreateRoomViewState.RoomType.Public) { setState { copy( - roomType = CreateRoomViewState.RoomType.Public(action.aliasLocalPart) + roomType = CreateRoomViewState.RoomType.Public(action.aliasLocalPart), + // Reset any error in the form about alias + asyncCreateRoomRequest = Uninitialized ) } } From 822ce41b54414aab2c4b9f79a5aae991a404164e Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Wed, 18 Nov 2020 14:22:07 +0000 Subject: [PATCH 100/231] Remove redundant return Signed-off-by: Dominic Fischer --- .../matrix/android/sdk/internal/session/group/DefaultGroup.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt index b47979775a..4f610fd81b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroup.kt @@ -23,6 +23,6 @@ internal class DefaultGroup(override val groupId: String, override suspend fun fetchGroupData() { val params = GetGroupDataTask.Params.FetchWithIds(listOf(groupId)) - return getGroupDataTask.execute(params) + getGroupDataTask.execute(params) } } From 92a6e9ea5ae0ac0bbb759fec31a88d30eae396ac Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Wed, 18 Nov 2020 14:23:59 +0000 Subject: [PATCH 101/231] Remove redundant return Signed-off-by: Dominic Fischer --- .../session/notification/DefaultPushRuleService.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index f55835eb62..e00d2ff26c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -103,19 +103,19 @@ internal class DefaultPushRuleService @Inject constructor( override suspend fun updatePushRuleEnableStatus(kind: RuleKind, pushRule: PushRule, enabled: Boolean) { // The rules will be updated, and will come back from the next sync response - return updatePushRuleEnableStatusTask.execute(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) + updatePushRuleEnableStatusTask.execute(UpdatePushRuleEnableStatusTask.Params(kind, pushRule, enabled)) } override suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) { - return addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule)) + addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule)) } override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) { - return updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) + updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) } override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) { - return removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule)) + removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule)) } override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) { From 796ba72bde39a298caa5bfe8a11f918c5d696529 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Wed, 18 Nov 2020 14:27:46 +0000 Subject: [PATCH 102/231] Reorder Signed-off-by: Dominic Fischer --- .../VectorSettingsAdvancedNotificationPreferenceFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt index 7e4520e4d4..8d9f8d7170 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedNotificationPreferenceFragment.kt @@ -60,10 +60,10 @@ class VectorSettingsAdvancedNotificationPreferenceFragment @Inject constructor() if (!isAdded) { return@launch } + hideLoadingView() result.onSuccess { preference.setPushRule(ruleAndKind.copy(pushRule = newRule)) } - hideLoadingView() result.onFailure { failure -> // Restore the previous value refreshDisplay() From 702d21fbc38fcad79f09b4d0cb2bd7a27edbf207 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 15:27:59 +0100 Subject: [PATCH 103/231] No encryption for public room (#1314) --- .../createroom/CreateRoomController.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) 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 740e73b955..235fddf9d4 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 @@ -116,22 +116,23 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin listener?.setAliasLocalPart(value) } } - } - formSwitchItem { - id("encryption") - enabled(enableFormElement) - title(stringProvider.getString(R.string.create_room_encryption_title)) - summary( - if (viewState.hsAdminHasDisabledE2E) { - stringProvider.getString(R.string.settings_hs_admin_e2e_disabled) - } else { - stringProvider.getString(R.string.create_room_encryption_description) - } - ) - switchChecked(viewState.isEncrypted) + } else { + formSwitchItem { + id("encryption") + enabled(enableFormElement) + title(stringProvider.getString(R.string.create_room_encryption_title)) + summary( + if (viewState.hsAdminHasDisabledE2E) { + stringProvider.getString(R.string.settings_hs_admin_e2e_disabled) + } else { + stringProvider.getString(R.string.create_room_encryption_description) + } + ) + switchChecked(viewState.isEncrypted) - listener { value -> - listener?.setIsEncrypted(value) + listener { value -> + listener?.setIsEncrypted(value) + } } } formAdvancedToggleItem { From 57d55e05e745117c3bc45ac1adba5437f29324cc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 15:36:05 +0100 Subject: [PATCH 104/231] Update comment --- .../features/roomdirectory/createroom/CreateRoomController.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 235fddf9d4..aaf7b6ead5 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 @@ -96,8 +96,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin listener?.setIsPublic(value) } } - // Room alias if (viewState.roomType is CreateRoomViewState.RoomType.Public) { + // Room alias for public room roomAliasEditItem { id("alias") enabled(enableFormElement) @@ -117,6 +117,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin } } } else { + // Room encryption for private room formSwitchItem { id("encryption") enabled(enableFormElement) From 1359c6be1d4ca3e1da650d46d35395c0de630e24 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Wed, 18 Nov 2020 15:40:22 +0000 Subject: [PATCH 105/231] Missed a spot Signed-off-by: Dominic Fischer --- .../android/sdk/internal/session/group/GroupFactory.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt index 31450763d8..653d2a6933 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupFactory.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.group import org.matrix.android.sdk.api.session.group.Group import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject internal interface GroupFactory { @@ -26,14 +25,12 @@ internal interface GroupFactory { } @SessionScope -internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask, - private val taskExecutor: TaskExecutor) : +internal class DefaultGroupFactory @Inject constructor(private val getGroupDataTask: GetGroupDataTask) : GroupFactory { override fun create(groupId: String): Group { return DefaultGroup( groupId = groupId, - taskExecutor = taskExecutor, getGroupDataTask = getGroupDataTask ) } From 2626a761ea2aff210c62ed97e1cf45068cabbb7e Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 10 Nov 2020 09:43:54 +0100 Subject: [PATCH 106/231] EmptyRoom tile with quick actions --- .../api/session/permalinks/MatrixLinkify.kt | 16 +- .../session/room/RoomAvatarResolver.kt | 12 +- .../room/alias/GetRoomIdByAliasTask.kt | 12 +- .../membership/RoomDisplayNameResolver.kt | 32 ++-- .../room/membership/RoomMemberHelper.kt | 4 + .../src/main/res/values/strings.xml | 6 + .../home/room/detail/RoomDetailAction.kt | 8 + .../home/room/detail/RoomDetailFragment.kt | 32 +++- .../home/room/detail/RoomDetailViewEvents.kt | 7 + .../home/room/detail/RoomDetailViewModel.kt | 30 ++++ .../factory/MergedHeaderItemFactory.kt | 17 +- .../timeline/item/MergedRoomCreationItem.kt | 162 ++++++++++++++---- .../features/navigation/DefaultNavigator.kt | 4 +- .../app/features/navigation/Navigator.kt | 2 +- .../roomprofile/RoomProfileActivity.kt | 16 +- .../src/main/res/drawable/ic_add_people.xml | 10 ++ ...meline_event_merged_room_creation_stub.xml | 159 +++++++++++++++-- vector/src/main/res/values/strings.xml | 10 ++ 18 files changed, 468 insertions(+), 71 deletions(-) create mode 100644 vector/src/main/res/drawable/ic_add_people.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt index 7f264c6228..5e9f3e1eb9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.permalinks import android.text.Spannable +import org.matrix.android.sdk.api.MatrixPatterns /** * MatrixLinkify take a piece of text and turns all of the @@ -35,7 +36,7 @@ object MatrixLinkify { * I disable it because it mess up with pills, and even with pills, it does not work correctly: * The url is not correct. Ex: for @user:matrix.org, the url will be @user:matrix.org, instead of a matrix.to */ - /* + // sanity checks if (spannable.isEmpty()) { return false @@ -48,14 +49,21 @@ object MatrixLinkify { val startPos = match.range.first if (startPos == 0 || text[startPos - 1] != '/') { val endPos = match.range.last + 1 - val url = text.substring(match.range) + var url = text.substring(match.range) + if (MatrixPatterns.isUserId(url) + || MatrixPatterns.isRoomAlias(url) + || MatrixPatterns.isRoomId(url) + || MatrixPatterns.isGroupId(url) + || MatrixPatterns.isEventId(url)) { + url = PermalinkService.MATRIX_TO_URL_BASE + url + } val span = MatrixPermalinkSpan(url, callback) spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } } return hasMatch - */ - return false + +// return false } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt index 90ee99a919..58633c39ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt @@ -46,11 +46,13 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId val roomMembers = RoomMemberHelper(realm, roomId) val members = roomMembers.queryActiveRoomMembersEvent().findAll() // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) - if (members.size == 1) { - res = members.firstOrNull()?.avatarUrl - } else if (members.size == 2) { - val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst() - res = firstOtherMember?.avatarUrl + if (roomMembers.isDirectRoom()) { + if (members.size == 1) { + res = members.firstOrNull()?.avatarUrl + } else if (members.size == 2) { + val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst() + res = firstOtherMember?.avatarUrl + } } return res } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index 8b011980d0..ebbd3b041a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task import io.realm.Realm import org.greenrobot.eventbus.EventBus +import timber.log.Timber import javax.inject.Inject internal interface GetRoomIdByAliasTask : Task> { @@ -50,9 +51,14 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( } else if (!params.searchOnServer) { Optional.from(null) } else { - roomId = executeRequest(eventBus) { - apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) - }.roomId + roomId = try { + executeRequest(eventBus) { + apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) + }.roomId + } catch (throwable: Throwable) { + Timber.d(throwable, "## Failed to get roomId from alias") + null + } Optional.from(roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 7f3796c1ce..73ae66a5b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -93,6 +93,9 @@ internal class RoomDisplayNameResolver @Inject constructor( } } else if (roomEntity?.membership == Membership.JOIN) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() + val invitedCount = roomSummary?.invitedMembersCount ?: 0 + val joinedCount = roomSummary?.joinedMembersCount ?: 0 + val othersTotalCount = invitedCount + joinedCount - 1 val otherMembersSubset: List = if (roomSummary?.heroes?.isNotEmpty() == true) { roomSummary.heroes.mapNotNull { userId -> roomMembers.getLastRoomMember(userId)?.takeIf { @@ -102,22 +105,29 @@ internal class RoomDisplayNameResolver @Inject constructor( } else { activeMembers.where() .notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) - .limit(3) + .limit(5) .findAll() .createSnapshot() } val otherMembersCount = otherMembersSubset.count() name = when (otherMembersCount) { - 0 -> stringProvider.getString(R.string.room_displayname_empty_room) - 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) - 2 -> stringProvider.getString(R.string.room_displayname_two_members, - resolveRoomMemberName(otherMembersSubset[0], roomMembers), - resolveRoomMemberName(otherMembersSubset[1], roomMembers) - ) - else -> stringProvider.getQuantityString(R.plurals.room_displayname_three_and_more_members, - roomMembers.getNumberOfJoinedMembers() - 1, - resolveRoomMemberName(otherMembersSubset[0], roomMembers), - roomMembers.getNumberOfJoinedMembers() - 1) + 0 -> { + stringProvider.getString(R.string.room_displayname_empty_room) + // TODO (was xx and yyy) ... + } + 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) + else -> { + val names = otherMembersSubset.map { + resolveRoomMemberName(it, roomMembers) ?: "" + } + if (otherMembersCount <= othersTotalCount) { + val remainingCount = invitedCount + joinedCount - names.size + (names.joinToString("${stringProvider.getString(R.string.room_displayname_separator)} ") + + " " + stringProvider.getQuantityString(R.plurals.and_n_others, remainingCount, remainingCount)) + } else { + names.dropLast(1).joinToString(", ") + " & ${names.last()}" + } + } } } return name ?: roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt index 7105a2cc22..c18eb0936b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt @@ -98,6 +98,10 @@ internal class RoomMemberHelper(private val realm: Realm, return getNumberOfJoinedMembers() + getNumberOfInvitedMembers() } + fun isDirectRoom() : Boolean { + return roomSummary?.isDirect ?: false + } + /** * Return all the roomMembers ids which are joined or invited to the room * diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index de30a64c32..c391a4edc8 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -180,8 +180,14 @@ %1$s and 1 other %1$s and %2$d others + + & %d other + & %d others + + , Empty room + Empty room (was %s) Initial Sync:\nImporting account… Initial Sync:\nImporting crypto diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 99adc0bf83..8891218a11 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -16,6 +16,8 @@ package im.vector.app.features.home.room.detail +import android.net.Uri +import android.view.View import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Event @@ -24,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.widgets.model.Widget +import org.matrix.android.sdk.api.util.MatrixItem sealed class RoomDetailAction : VectorViewModelAction { data class UserIsTyping(val isTyping: Boolean) : RoomDetailAction() @@ -90,4 +93,9 @@ sealed class RoomDetailAction : VectorViewModelAction { data class OpenOrCreateDm(val userId: String) : RoomDetailAction() data class JumpToReadReceipt(val userId: String) : RoomDetailAction() + object QuickActionInvitePeople : RoomDetailAction() + object QuickActionSetAvatar : RoomDetailAction() + data class SetAvatarAction(val newAvatarUri: Uri, val newAvatarFileName: String) : RoomDetailAction() + object QuickActionSetTopic : RoomDetailAction() + data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val transitionView: View?) : RoomDetailAction() } 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 9c6c473a7f..bec84f6479 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 @@ -71,6 +71,7 @@ import com.google.android.material.textfield.TextInputEditText import com.jakewharton.rxbinding3.widget.textChanges import im.vector.app.R import im.vector.app.core.dialogs.ConfirmationDialogBuilder +import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.dialogs.withColoredButton import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.extensions.cleanup @@ -82,6 +83,7 @@ import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideRequests +import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider @@ -149,6 +151,7 @@ import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.reactions.EmojiReactionPickerActivity +import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData @@ -196,6 +199,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber import java.io.File import java.net.URL +import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -229,7 +233,7 @@ class RoomDetailFragment @Inject constructor( JumpToReadMarkerView.Callback, AttachmentTypeSelectorView.Callback, AttachmentsHelper.Callback, -// RoomWidgetsBannerView.Callback, + GalleryOrCameraDialogHelper.Listener, ActiveCallView.Callback { companion object { @@ -250,6 +254,8 @@ class RoomDetailFragment @Inject constructor( private const val ircPattern = " (IRC)" } + private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) + private val roomDetailArgs: RoomDetailArgs by args() private val glideRequests by lazy { GlideApp.with(this) @@ -364,6 +370,12 @@ class RoomDetailFragment @Inject constructor( 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) + } }.exhaustive } @@ -372,6 +384,24 @@ class RoomDetailFragment @Inject constructor( } } + override fun onImageReady(uri: Uri?) { + uri ?: return + roomDetailViewModel.handle( + RoomDetailAction.SetAvatarAction( + newAvatarUri = uri, + newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString() + ) + ) + } + + private fun handleOpenRoomSettings() { + navigator.openRoomProfile( + requireContext(), + roomDetailArgs.roomId, + RoomProfileActivity.EXTRA_DIRECT_ACCESS_ROOM_SETTINGS + ) + } + private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) { navigator.openRoom(requireContext(), openRoom.roomId, null) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt index b9e3e6b31d..d5d94a0ca5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt @@ -17,10 +17,12 @@ package im.vector.app.features.home.room.detail import android.net.Uri +import android.view.View import androidx.annotation.StringRes import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.command.Command import org.matrix.android.sdk.api.session.widgets.model.Widget +import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import java.io.File @@ -43,6 +45,11 @@ sealed class RoomDetailViewEvents : VectorViewEvents { data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents() data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents() + object OpenInvitePeople : RoomDetailViewEvents() + object OpenSetRoomAvatarDialog : RoomDetailViewEvents() + object OpenRoomSettings : RoomDetailViewEvents() + data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val view: View?) : RoomDetailViewEvents() + object ShowWaitingView : RoomDetailViewEvents() object HideWaitingView : RoomDetailViewEvents() 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 98bcdbe60e..beaecbb898 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 @@ -277,9 +277,39 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.CancelSend -> handleCancel(action) is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) + RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() + RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() + is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) + RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) + is RoomDetailAction.ShowRoomAvatarFullScreen -> { + _viewEvents.post( + RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) + ) + } }.exhaustive } + private fun handleSetNewAvatar(action: RoomDetailAction.SetAvatarAction) { + viewModelScope.launch(Dispatchers.IO) { + try { + awaitCallback { + room.updateAvatar(action.newAvatarUri, action.newAvatarFileName, it) + } + _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) + } catch (failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) + } + } + } + + private fun handleInvitePeople() { + _viewEvents.post(RoomDetailViewEvents.OpenInvitePeople) + } + + private fun handleQuickSetAvatar() { + _viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog) + } + private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) { val existingDmRoomId = session.getExistingDirectRoomWithUser(action.userId) if (existingDmRoomId == null) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index e7a911ceb1..23bd041e95 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -31,10 +31,14 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEve import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_ import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_ +import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.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.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent @@ -187,6 +191,11 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde collapsedEventIds.removeAll(mergedEventIds) } val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() } + val powerLevelsHelper = roomSummaryHolder.roomSummary?.roomId + ?.let { activeSessionHolder.getSafeActiveSession()?.getRoom(it) } + ?.let { it.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)?.content?.toModel() } + ?.let { PowerLevelsHelper(it) } + val currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: "" val attributes = MergedRoomCreationItem.Attributes( isCollapsed = isCollapsed, mergeData = mergedData, @@ -198,13 +207,19 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde hasEncryptionEvent = hasEncryption, isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM, readReceiptsCallback = callback, - currentUserId = activeSessionHolder.getSafeActiveSession()?.myUserId ?: "" + callback = callback, + currentUserId = currentUserId, + roomSummary = roomSummaryHolder.roomSummary, + canChangeAvatar = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_AVATAR) ?: false, + canChangeTopic = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_TOPIC) ?: false, + canChangeName = powerLevelsHelper?.isUserAllowedToSend(currentUserId, true, EventType.STATE_ROOM_NAME) ?: false ) MergedRoomCreationItem_() .id(mergeId) .leftGuideline(avatarSizeProvider.leftGuideline) .highlighted(isCollapsed && highlighted) .attributes(attributes) + .movementMethod(createLinkMovementMethod(callback)) .also { it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index 1896a812fc..a1c0b869e2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -16,11 +16,14 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.text.SpannableString +import android.text.method.MovementMethod +import android.text.style.ClickableSpan import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.RelativeLayout import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible @@ -28,8 +31,16 @@ import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.utils.DebouncedClickListener +import im.vector.app.core.utils.tappableMatchingText import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.tools.linkify +import me.gujun.android.span.span +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.toMatrixItem @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) abstract class MergedRoomCreationItem : BasedMergedItem() { @@ -37,11 +48,16 @@ abstract class MergedRoomCreationItem : BasedMergedItem { - this.marginEnd = leftGuideline - } - if (attributes.isEncryptionAlgorithmSecure) { - holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled) - holder.e2eTitleDescriptionView.text = if (data?.isDirectRoom == true) { - holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description) - } else { - holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) - } - holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER - holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( - ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), - null, null, null - ) - } else { - holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled) - holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description) - holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( - ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning), - null, null, null - ) - } - } else { - holder.encryptionTile.isVisible = false - } + bindEncryptionTile(holder, data) } else { holder.avatarView.visibility = View.INVISIBLE holder.summaryView.visibility = View.GONE @@ -107,6 +96,107 @@ abstract class MergedRoomCreationItem : BasedMergedItem { + this.marginEnd = leftGuideline + } + if (attributes.isEncryptionAlgorithmSecure) { + holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled) + holder.e2eTitleDescriptionView.text = if (data?.isDirectRoom == true) { + holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description) + } else { + holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) + } + holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER + holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), + null, null, null + ) + } else { + holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled) + holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description) + holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning), + null, null, null + ) + } + } else { + holder.encryptionTile.isVisible = false + } + } + + private fun bindCreationSummaryTile(holder: Holder) { + val roomSummary = attributes.roomSummary + val roomDisplayName = roomSummary?.displayName + holder.roomNameText.setTextOrHide(roomDisplayName) + val isDirect = roomSummary?.isDirect == true + val membersCount = roomSummary?.otherMemberIds?.size ?: 0 + + if (isDirect) { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_dm, roomSummary?.displayName ?: "") + } else if (roomDisplayName.isNullOrBlank() || roomSummary.name.isBlank()) { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name) + } else { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName) + } + + val topic = roomSummary?.topic + if (topic.isNullOrBlank()) { + // do not show hint for DMs or group DMs + if (!isDirect) { + val addTopicLink = holder.view.resources.getString(R.string.add_a_topic_link_text) + val styledText = SpannableString(holder.view.resources.getString(R.string.room_created_summary_no_topic_creation_text, addTopicLink)) + holder.roomTopicText.setTextOrHide(styledText.tappableMatchingText(addTopicLink, object : ClickableSpan() { + override fun onClick(widget: View) { + attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetTopic) + } + })) + } + } else { + holder.roomTopicText.setTextOrHide(span { + span(holder.view.resources.getString(R.string.topic_prefix)) { + textStyle = "bold" + } + +topic.linkify(attributes.callback) + } + ) + } + holder.roomTopicText.movementMethod = movementMethod + val roomItem = roomSummary?.toMatrixItem() + if (roomItem != null) { + holder.roomAvatarImageView.isVisible = true + attributes.avatarRenderer.render(roomItem, holder.roomAvatarImageView) + holder.roomAvatarImageView.setOnClickListener(DebouncedClickListener({ view -> + attributes.callback?.onTimelineItemAction(RoomDetailAction.ShowRoomAvatarFullScreen(roomItem, view)) + })) + } else { + holder.roomAvatarImageView.isVisible = false + } + + if (isDirect) { + holder.addPeopleButton.isVisible = false + } else { + holder.addPeopleButton.isVisible = true + holder.addPeopleButton.setOnClickListener(DebouncedClickListener({ _ -> + attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionInvitePeople) + })) + } + + val shouldShowSetAvatar = attributes.canChangeAvatar + && (roomSummary?.isDirect == false || (isDirect && membersCount >= 2)) + + if (shouldShowSetAvatar && roomItem?.avatarUrl.isNullOrBlank()) { + holder.setAvatarButton.isVisible = true + holder.setAvatarButton.setOnClickListener(DebouncedClickListener({ _ -> + attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar) + })) + } else { + holder.setAvatarButton.isVisible = false + } + } + class Holder : BasedMergedItem.Holder(STUB_ID) { val summaryView by bind(R.id.itemNoticeTextView) val avatarView by bind(R.id.itemNoticeAvatarView) @@ -114,6 +204,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem(R.id.itemVerificationDoneTitleTextView) val e2eTitleDescriptionView by bind(R.id.itemVerificationDoneDetailTextView) + + val roomNameText by bind(R.id.roomNameTileText) + val roomDescriptionText by bind(R.id.roomNameDescriptionText) + val roomTopicText by bind(R.id.roomNameTopicText) + val roomAvatarImageView by bind(R.id.roomAvatarImageView) + val addPeopleButton by bind(R.id.creationTileAddPeopleButton) + val setAvatarButton by bind(R.id.creationTileSetAvatarButton) } companion object { @@ -126,8 +223,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem Unit, + val callback: TimelineEventController.Callback? = null, val currentUserId: String, val hasEncryptionEvent: Boolean, - val isEncryptionAlgorithmSecure: Boolean + val isEncryptionAlgorithmSecure: Boolean, + val roomSummary: RoomSummary?, + val canChangeAvatar: Boolean = false, + val canChangeName: Boolean = false, + val canChangeTopic: Boolean = false ) : BasedMergedItem.Attributes } 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 106d804cd3..2d0ca86d52 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 @@ -248,8 +248,8 @@ class DefaultNavigator @Inject constructor( context.startActivity(KeysBackupManageActivity.intent(context)) } - override fun openRoomProfile(context: Context, roomId: String) { - context.startActivity(RoomProfileActivity.newIntent(context, roomId)) + override fun openRoomProfile(context: Context, roomId: String, directAccess: Int?) { + context.startActivity(RoomProfileActivity.newIntent(context, roomId, directAccess)) } override fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) { 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 1d01a5e4f0..504fccb63a 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 @@ -78,7 +78,7 @@ interface Navigator { fun openRoomMemberProfile(userId: String, roomId: String?, context: Context, buildTask: Boolean = false) - fun openRoomProfile(context: Context, roomId: String) + fun openRoomProfile(context: Context, roomId: String, directAccess: Int? = null) fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 734620e378..609042ffa4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -46,10 +46,16 @@ class RoomProfileActivity : companion object { - fun newIntent(context: Context, roomId: String): Intent { + private const val EXTRA_DIRECT_ACCESS = "EXTRA_DIRECT_ACCESS" + + const val EXTRA_DIRECT_ACCESS_ROOM_ROOT = 0 + const val EXTRA_DIRECT_ACCESS_ROOM_SETTINGS = 1 + + fun newIntent(context: Context, roomId: String, directAccess: Int?): Intent { val roomProfileArgs = RoomProfileArgs(roomId) return Intent(context, RoomProfileActivity::class.java).apply { putExtra(MvRx.KEY_ARG, roomProfileArgs) + putExtra(EXTRA_DIRECT_ACCESS, directAccess) } } } @@ -80,7 +86,13 @@ class RoomProfileActivity : sharedActionViewModel = viewModelProvider.get(RoomProfileSharedActionViewModel::class.java) roomProfileArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return if (isFirstCreation()) { - addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) + when (intent?.extras?.getInt(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOM_ROOT)) { + EXTRA_DIRECT_ACCESS_ROOM_SETTINGS -> { + addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs) + } + else -> addFragment(R.id.simpleFragmentContainer, RoomProfileFragment::class.java, roomProfileArgs) + } } sharedActionViewModel .observe() diff --git a/vector/src/main/res/drawable/ic_add_people.xml b/vector/src/main/res/drawable/ic_add_people.xml new file mode 100644 index 0000000000..3ec60095ff --- /dev/null +++ b/vector/src/main/res/drawable/ic_add_people.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml index eefa387254..6fdd30b9e2 100644 --- a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml @@ -1,31 +1,168 @@ - + android:layout_marginTop="0dp"> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_below="@+id/creationTile" + android:layout_marginTop="8dp"> You created and configured the room. %s joined. You joined. + This is the beginning of %s. + This is the beginning of this conversation. + This is the beginning of your direct message history with %s. + + %s to let people know what this room is about. + Add a topic + "Topic: " Almost there! Is the other device showing the same shield? Almost there! Waiting for confirmation… @@ -2511,6 +2518,7 @@ "We couldn't create your DM. Please check the users you want to invite and try again." Add members + Add people INVITE Inviting users… Invite Users @@ -2575,6 +2583,8 @@ Room Name Topic You changed room settings successfully + Set avatar + You cannot access this message Waiting for this message, this may take a while From 264bc52bcc2d8844765201892c9869e7ed9d5cbd Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 13 Nov 2020 09:27:34 +0100 Subject: [PATCH 107/231] WIP review --- .../session/room/alias/GetRoomIdByAliasTask.kt | 15 ++++++--------- .../timeline/item/MergedRoomCreationItem.kt | 13 +++++++------ .../RoomMemberProfileController.kt | 2 +- ...m_timeline_event_merged_room_creation_stub.xml | 2 +- vector/src/main/res/values/strings.xml | 2 +- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index ebbd3b041a..58a119cc77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -17,6 +17,9 @@ package org.matrix.android.sdk.internal.session.room.alias import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.findByAlias @@ -24,9 +27,6 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import io.realm.Realm -import org.greenrobot.eventbus.EventBus -import timber.log.Timber import javax.inject.Inject internal interface GetRoomIdByAliasTask : Task> { @@ -51,14 +51,11 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( } else if (!params.searchOnServer) { Optional.from(null) } else { - roomId = try { + roomId = tryOrNull("## Failed to get roomId from alias") { executeRequest(eventBus) { apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) - }.roomId - } catch (throwable: Throwable) { - Timber.d(throwable, "## Failed to get roomId from alias") - null - } + } + }?.roomId Optional.from(roomId) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index a1c0b869e2..dcdb2bab29 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -155,12 +155,13 @@ abstract class MergedRoomCreationItem : BasedMergedItem + tools:text="@string/room_created_summary_no_topic_creation_text" /> This is the beginning of this conversation. This is the beginning of your direct message history with %s. - %s to let people know what this room is about. + %s to let people know what this room is about. Add a topic "Topic: " From 1de5cd2e61b53d00c0289bff5f7333962c97c239 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 16 Nov 2020 09:56:26 +0100 Subject: [PATCH 108/231] Code review --- .../session/room/RoomAvatarResolver.kt | 5 ++- .../membership/RoomDisplayNameResolver.kt | 38 ++++++++++++++----- .../room/membership/RoomMemberHelper.kt | 8 +--- .../src/main/res/values/strings.xml | 8 ++-- .../RoomMemberProfileController.kt | 2 +- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt index 58633c39ba..99f9d3644d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt @@ -26,6 +26,8 @@ import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import io.realm.Realm +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.query.where import javax.inject.Inject internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) { @@ -46,7 +48,8 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId val roomMembers = RoomMemberHelper(realm, roomId) val members = roomMembers.queryActiveRoomMembersEvent().findAll() // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) - if (roomMembers.isDirectRoom()) { + val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false + if (isDirectRoom) { if (members.size == 1) { res = members.firstOrNull()?.avatarUrl } else if (members.size == 2) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 73ae66a5b2..8d0789d675 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -116,17 +116,35 @@ internal class RoomDisplayNameResolver @Inject constructor( // TODO (was xx and yyy) ... } 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) + 2 -> { + stringProvider.getString(R.string.room_displayname_two_members, + resolveRoomMemberName(otherMembersSubset[0], roomMembers), + resolveRoomMemberName(otherMembersSubset[1], roomMembers) + ) + } + 3 -> { + stringProvider.getString(R.string.room_displayname_3_members, + resolveRoomMemberName(otherMembersSubset[0], roomMembers), + resolveRoomMemberName(otherMembersSubset[1], roomMembers), + resolveRoomMemberName(otherMembersSubset[2], roomMembers) + ) + } + 4 -> { + stringProvider.getString(R.string.room_displayname_4_members, + resolveRoomMemberName(otherMembersSubset[0], roomMembers), + resolveRoomMemberName(otherMembersSubset[1], roomMembers), + resolveRoomMemberName(otherMembersSubset[2], roomMembers), + resolveRoomMemberName(otherMembersSubset[3], roomMembers) + ) + } else -> { - val names = otherMembersSubset.map { - resolveRoomMemberName(it, roomMembers) ?: "" - } - if (otherMembersCount <= othersTotalCount) { - val remainingCount = invitedCount + joinedCount - names.size - (names.joinToString("${stringProvider.getString(R.string.room_displayname_separator)} ") - + " " + stringProvider.getQuantityString(R.plurals.and_n_others, remainingCount, remainingCount)) - } else { - names.dropLast(1).joinToString(", ") + " & ${names.last()}" - } + val remainingCount = invitedCount + joinedCount - otherMembersCount + 1 + stringProvider.getString(R.string.room_displayname_four_and_more_members, + resolveRoomMemberName(otherMembersSubset[0], roomMembers), + resolveRoomMemberName(otherMembersSubset[1], roomMembers), + resolveRoomMemberName(otherMembersSubset[2], roomMembers), + remainingCount + ) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt index c18eb0936b..2a7c46bd42 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.session.room.membership +import io.realm.Realm +import io.realm.RealmQuery import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity @@ -25,8 +27,6 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where -import io.realm.Realm -import io.realm.RealmQuery /** * This class is an helper around STATE_ROOM_MEMBER events. @@ -98,10 +98,6 @@ internal class RoomMemberHelper(private val realm: Realm, return getNumberOfJoinedMembers() + getNumberOfInvitedMembers() } - fun isDirectRoom() : Boolean { - return roomSummary?.isDirect ?: false - } - /** * Return all the roomMembers ids which are joined or invited to the room * diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index c391a4edc8..130ad5570c 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -175,16 +175,14 @@ %1$s and %2$s + %1$s, %2$s and %3$s + %1$s, %2$s, %3$s and %4$s + %1$s, %2$s, %3$s and %4$d others %1$s and 1 other %1$s and %2$d others - - & %d other - & %d others - - , Empty room Empty room (was %s) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 44e726735f..2e91091443 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -161,7 +161,7 @@ class RoomMemberProfileController @Inject constructor( } else { genericFooterItem { id("verify_footer_not_encrypted") - text(RRstringProvider.getString(R.string.room_profile_not_encrypted_subtitle)) + text(stringProvider.getString(R.string.room_profile_not_encrypted_subtitle)) centered(false) } } From 206e68b1d202a30523a59eaddd2081b8a24cf3d0 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 18 Nov 2020 10:17:50 +0100 Subject: [PATCH 109/231] Unused val --- .../internal/session/room/membership/RoomDisplayNameResolver.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 8d0789d675..c69773fbeb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -95,7 +95,6 @@ internal class RoomDisplayNameResolver @Inject constructor( val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val invitedCount = roomSummary?.invitedMembersCount ?: 0 val joinedCount = roomSummary?.joinedMembersCount ?: 0 - val othersTotalCount = invitedCount + joinedCount - 1 val otherMembersSubset: List = if (roomSummary?.heroes?.isNotEmpty() == true) { roomSummary.heroes.mapNotNull { userId -> roomMembers.getLastRoomMember(userId)?.takeIf { From 1eac90e5b153879d9a2a16dba35ba98a87933699 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 16:07:32 +0100 Subject: [PATCH 110/231] Use plurals for proper i18n --- .../room/membership/RoomDisplayNameResolver.kt | 14 ++++++++------ matrix-sdk-android/src/main/res/values/strings.xml | 9 +++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index c69773fbeb..a7dfcfc96f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -110,25 +110,25 @@ internal class RoomDisplayNameResolver @Inject constructor( } val otherMembersCount = otherMembersSubset.count() name = when (otherMembersCount) { - 0 -> { + 0 -> { stringProvider.getString(R.string.room_displayname_empty_room) // TODO (was xx and yyy) ... } - 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) - 2 -> { + 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) + 2 -> { stringProvider.getString(R.string.room_displayname_two_members, resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers) ) } - 3 -> { + 3 -> { stringProvider.getString(R.string.room_displayname_3_members, resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers), resolveRoomMemberName(otherMembersSubset[2], roomMembers) ) } - 4 -> { + 4 -> { stringProvider.getString(R.string.room_displayname_4_members, resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers), @@ -138,7 +138,9 @@ internal class RoomDisplayNameResolver @Inject constructor( } else -> { val remainingCount = invitedCount + joinedCount - otherMembersCount + 1 - stringProvider.getString(R.string.room_displayname_four_and_more_members, + stringProvider.getQuantityString( + R.plurals.room_displayname_four_and_more_members, + remainingCount, resolveRoomMemberName(otherMembersSubset[0], roomMembers), resolveRoomMemberName(otherMembersSubset[1], roomMembers), resolveRoomMemberName(otherMembersSubset[2], roomMembers), diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 130ad5570c..f77cd3203d 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -175,10 +175,15 @@ %1$s and %2$s + %1$s, %2$s and %3$s + %1$s, %2$s, %3$s and %4$s - %1$s, %2$s, %3$s and %4$d others - + + + %1$s, %2$s, %3$s and %4$d other + %1$s, %2$s, %3$s and %4$d others + %1$s and 1 other %1$s and %2$d others From b82b378cfe386e394874a2a4ea46f4cf069ba1b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 17:00:44 +0100 Subject: [PATCH 111/231] Cleanup and changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 936e6b0ffe..e98329d91f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Features ✨: - Improvements 🙌: + - New room creation tile with quick action (#2346) - Open an existing DM instead of creating a new one (#2319) - Ask for explicit user consent to send their contact details to the identity server (#2375) - Handle events of type "m.room.server_acl" (#890) From 9ed8f26d7c78301ae1b619ab6c25e51a80e77d0c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 17:15:36 +0100 Subject: [PATCH 112/231] Make the room default avatar clickable to set it (as per the small picto) And do some cleanup --- .../timeline/item/MergedRoomCreationItem.kt | 43 ++++++++++--------- ...meline_event_merged_room_creation_stub.xml | 28 +++++------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index dcdb2bab29..34b9ae1b9d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -165,36 +165,37 @@ abstract class MergedRoomCreationItem : BasedMergedItem= 2)) + && roomItem?.avatarUrl.isNullOrBlank() + + holder.roomAvatarImageView.isVisible = roomItem != null if (roomItem != null) { - holder.roomAvatarImageView.isVisible = true attributes.avatarRenderer.render(roomItem, holder.roomAvatarImageView) holder.roomAvatarImageView.setOnClickListener(DebouncedClickListener({ view -> - attributes.callback?.onTimelineItemAction(RoomDetailAction.ShowRoomAvatarFullScreen(roomItem, view)) - })) - } else { - holder.roomAvatarImageView.isVisible = false - } - - if (isDirect) { - holder.addPeopleButton.isVisible = false - } else { - holder.addPeopleButton.isVisible = true - holder.addPeopleButton.setOnClickListener(DebouncedClickListener({ _ -> - attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionInvitePeople) + if (shouldSetAvatar) { + attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar) + } else { + // Note: this is no op if there is no avatar on the room + attributes.callback?.onTimelineItemAction(RoomDetailAction.ShowRoomAvatarFullScreen(roomItem, view)) + } })) } - val shouldShowSetAvatar = attributes.canChangeAvatar - && (roomSummary?.isDirect == false || (isDirect && membersCount >= 2)) - - if (shouldShowSetAvatar && roomItem?.avatarUrl.isNullOrBlank()) { - holder.setAvatarButton.isVisible = true + holder.setAvatarButton.isVisible = shouldSetAvatar + if (shouldSetAvatar) { holder.setAvatarButton.setOnClickListener(DebouncedClickListener({ _ -> attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetAvatar) })) - } else { - holder.setAvatarButton.isVisible = false + } + + holder.addPeopleButton.isVisible = !isDirect + if (!isDirect) { + holder.addPeopleButton.setOnClickListener(DebouncedClickListener({ _ -> + attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionInvitePeople) + })) } } @@ -209,7 +210,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem(R.id.roomNameTileText) val roomDescriptionText by bind(R.id.roomNameDescriptionText) val roomTopicText by bind(R.id.roomNameTopicText) - val roomAvatarImageView by bind(R.id.roomAvatarImageView) + val roomAvatarImageView by bind(R.id.creationTileRoomAvatarImageView) val addPeopleButton by bind(R.id.creationTileAddPeopleButton) val setAvatarButton by bind(R.id.creationTileSetAvatarButton) } diff --git a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml index 8dba361f5e..ccb9bacd30 100644 --- a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml @@ -3,16 +3,12 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - + android:layout_height="wrap_content"> + android:layout_height="wrap_content"> + @@ -74,8 +71,8 @@ android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/roomAvatarImageView" - tools:text="Room Name" /> + app:layout_constraintTop_toBottomOf="@id/creationTileRoomAvatarImageView" + tools:text="@sample/matrix.json/data/roomName" /> - + tools:text="@string/this_is_the_beginning_of_room_no_name" /> - + app:layout_constraintTop_toTopOf="@id/addPeopleButtonBg" /> + From c29e4648ea6a8a1a8d2bb3553f43b74103c7bd2d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Nov 2020 17:34:51 +0100 Subject: [PATCH 113/231] Small cleanup --- ...meline_event_merged_room_creation_stub.xml | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml index ccb9bacd30..728b90b696 100644 --- a/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_merged_room_creation_stub.xml @@ -102,7 +102,7 @@ app:layout_constraintTop_toBottomOf="@id/roomNameDescriptionText" tools:text="@string/room_created_summary_no_topic_creation_text" /> - - - - + android:importantForAccessibility="no" + android:scaleType="center" + android:src="@drawable/ic_add_people" /> + android:textColor="@color/riotx_accent" /> - + From c50c028c9eb2e871c6de8829d9d1dfac29953023 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Nov 2020 15:22:24 +0100 Subject: [PATCH 114/231] Code quality --- vector/src/main/res/layout/item_room_alias_text_input.xml | 5 ++--- vector/src/main/res/values/donottranslate.xml | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_room_alias_text_input.xml b/vector/src/main/res/layout/item_room_alias_text_input.xml index 44c76b631d..9216fc6b7e 100644 --- a/vector/src/main/res/layout/item_room_alias_text_input.xml +++ b/vector/src/main/res/layout/item_room_alias_text_input.xml @@ -12,11 +12,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/layout_horizontal_margin" - android:text="#" + android:text="@string/matrix_room_alias_prefix" android:textSize="18sp" app:layout_constraintBaseline_toBaselineOf="@+id/itemRoomAliasTextInputLayout" - app:layout_constraintStart_toStartOf="parent" - tools:ignore="HardcodedText" /> + app:layout_constraintStart_toStartOf="parent" /> ******** + # + Not implemented yet in Element From 029d8574f1ed8cb72975317f1dc6de5201993aea Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Wed, 18 Nov 2020 15:35:19 +0000 Subject: [PATCH 115/231] Translated using Weblate (Albanian) Currently translated at 99.4% (1923 of 1933 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 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 81d64d9669..ce76f7c387 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2177,4 +2177,8 @@ Subjekt Subjekt dhome (në daçi) Emër dhome + Eksporto Auditim + Mesazh i drejtpërdrejtë + Dërgo historik kërkesash për dhënie kyçesh + S’ka më përfundime \ No newline at end of file From eb7ee49096f6b87443785863cd5aa396ed4243f9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 09:04:04 +0100 Subject: [PATCH 116/231] Kotlin optimization form #1435 --- .../network/interceptors/FormattedJsonHttpLogger.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt index 5c03e8a855..630f6f1e29 100644 --- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt +++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/FormattedJsonHttpLogger.kt @@ -66,9 +66,9 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger { } private fun logJson(formattedJson: String) { - val arr = formattedJson.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - for (s in arr) { - Timber.v(s) - } + formattedJson + .lines() + .dropLastWhile { it.isEmpty() } + .forEach { Timber.v(it) } } } From 8fb3c68573c663e314f8b88ba75271e2313bad95 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 09:05:53 +0100 Subject: [PATCH 117/231] Kotlin optimization form #1435 --- .../android/sdk/api/session/permalinks/PermalinkParser.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index dc47c81a5f..347a3bb531 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -44,13 +44,12 @@ object PermalinkParser { if (fragment.isNullOrEmpty()) { return PermalinkData.FallbackLink(uri) } - val indexOfQuery = fragment.indexOf("?") - val safeFragment = if (indexOfQuery != -1) fragment.substring(0, indexOfQuery) else fragment + val safeFragment = fragment.substringBefore('?') val viaQueryParameters = fragment.getViaParameters() // we are limiting to 2 params val params = safeFragment - .split(MatrixPatterns.SEP_REGEX.toRegex()) + .split(MatrixPatterns.SEP_REGEX) .filter { it.isNotEmpty() } .take(2) From 9ce1222fd0c4407c8cf7227f7288e790362d8382 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 09:07:29 +0100 Subject: [PATCH 118/231] Kotlin optimization form #1435 --- .../crypto/crosssigning/DefaultCrossSigningService.kt | 3 +-- .../crypto/verification/SASDefaultVerificationTransaction.kt | 5 ++--- .../java/org/matrix/android/sdk/internal/util/StringUtils.kt | 2 -- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 1871dba0e2..bcad448eb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import org.matrix.android.sdk.internal.util.withoutPrefix import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo @@ -444,7 +443,7 @@ internal class DefaultCrossSigningService @Inject constructor( } else { // Maybe it's signed by a locally trusted device? myMasterKey.signatures?.get(userId)?.forEach { (key, value) -> - val potentialDeviceId = key.withoutPrefix("ed25519:") + val potentialDeviceId = key.removePrefix("ed25519:") val potentialDevice = myDevices?.firstOrNull { it.deviceId == potentialDeviceId } // cryptoStore.getUserDevice(userId, potentialDeviceId) if (potentialDevice != null && potentialDevice.isVerified) { // Check signature validity? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt index cb254eac3c..22a190c68e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.extensions.toUnsignedInt -import org.matrix.android.sdk.internal.util.withoutPrefix import org.matrix.olm.OlmSAS import org.matrix.olm.OlmUtility import timber.log.Timber @@ -250,7 +249,7 @@ internal abstract class SASDefaultVerificationTransaction( // cannot be empty because it has been validated theirMacSafe.mac.keys.forEach { - val keyIDNoPrefix = it.withoutPrefix("ed25519:") + val keyIDNoPrefix = it.removePrefix("ed25519:") val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint() if (otherDeviceKey == null) { Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify") @@ -273,7 +272,7 @@ internal abstract class SASDefaultVerificationTransaction( if (otherCrossSigningMasterKeyPublic != null) { // Did the user signed his master key theirMacSafe.mac.keys.forEach { - val keyIDNoPrefix = it.withoutPrefix("ed25519:") + val keyIDNoPrefix = it.removePrefix("ed25519:") if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) { // Check the signature val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt index 32997e2064..ecfbe311f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt @@ -50,8 +50,6 @@ fun convertFromUTF8(s: String): String { } } -fun String.withoutPrefix(prefix: String) = if (startsWith(prefix)) substringAfter(prefix) else this - /** * Returns whether a string contains an occurrence of another, as a standalone word, regardless of case. * From e8d084b85557610239fba56fa4a8d85c69183ecf Mon Sep 17 00:00:00 2001 From: TR-SLimey <37966924+TR-SLimey@users.noreply.github.com> Date: Fri, 6 Nov 2020 13:04:21 +0000 Subject: [PATCH 119/231] Add ability to share profile by QR code --- AUTHORS.md | 8 +- CHANGES.md | 2 +- .../createdirect/CreateDirectRoomActivity.kt | 74 ++++--- .../CreateDirectRoomByQrCodeFragment.kt | 108 ++++++++++ .../createdirect/CreateDirectRoomViewModel.kt | 2 +- .../room/filtered/FilteredRoomFooterItem.kt | 4 +- .../home/room/list/RoomListFragment.kt | 28 ++- .../home/room/list/widget/DmsFabMenuView.kt | 102 +++++++++ .../{FabMenuView.kt => NotifsFabMenuView.kt} | 8 +- .../features/navigation/DefaultNavigator.kt | 4 +- .../app/features/navigation/Navigator.kt | 2 +- .../roomdirectory/picker/RoomDirectoryItem.kt | 4 +- .../RoomMemberProfileFragment.kt | 20 +- .../main/res/drawable/ic_fab_add_by_mxid.xml | 30 +++ .../res/drawable/ic_fab_add_by_qr_code.xml | 30 +++ .../main/res/layout/dialog_share_qr_code.xml | 16 ++ .../main/res/layout/fragment_room_list.xml | 20 +- .../res/layout/motion_dms_fab_menu_merge.xml | 79 +++++++ ...e.xml => motion_notifs_fab_menu_merge.xml} | 2 +- vector/src/main/res/values/strings.xml | 9 + .../res/xml/motion_scene_dms_fab_menu.xml | 199 ++++++++++++++++++ ...u.xml => motion_scene_notifs_fab_menu.xml} | 0 22 files changed, 680 insertions(+), 71 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt rename vector/src/main/java/im/vector/app/features/home/room/list/widget/{FabMenuView.kt => NotifsFabMenuView.kt} (88%) create mode 100644 vector/src/main/res/drawable/ic_fab_add_by_mxid.xml create mode 100644 vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml create mode 100644 vector/src/main/res/layout/dialog_share_qr_code.xml create mode 100644 vector/src/main/res/layout/motion_dms_fab_menu_merge.xml rename vector/src/main/res/layout/{motion_fab_menu_merge.xml => motion_notifs_fab_menu_merge.xml} (98%) create mode 100644 vector/src/main/res/xml/motion_scene_dms_fab_menu.xml rename vector/src/main/res/xml/{motion_scene_fab_menu.xml => motion_scene_notifs_fab_menu.xml} (100%) diff --git a/AUTHORS.md b/AUTHORS.md index 823dbc7311..ad20133d83 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -39,7 +39,7 @@ We do not forget all translators, for their work of translating Element into man Feel free to add your name below, when you contribute to the project! -Name | Matrix ID | GitHub ---------|---------------------|-------------------------------------- -gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower) - +Name | Matrix ID | GitHub +----------|-----------------------------|-------------------------------------- +gjpower | @gjpower:matrix.org | [gjpower](https://github.com/gjpower) +TR_SLimey | @tr_slimey:an-atom-in.space | [TR-SLimey](https://github.com/TR-SLimey) diff --git a/CHANGES.md b/CHANGES.md index 80bb2c66b8..4873a999b6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in Element 1.0.11 (2020-XX-XX) =================================================== Features ✨: - - + - Create DMs with users by scanning their QR code (#2025) Improvements 🙌: - New room creation tile with quick action (#2346) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 10ab1673e4..2035ee50f6 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -22,6 +22,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View +import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -37,6 +38,8 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH +import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS import im.vector.app.core.utils.allGranted import im.vector.app.core.utils.checkPermissions @@ -72,35 +75,45 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { super.onCreate(savedInstanceState) toolbar.visibility = View.GONE sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java) - sharedActionViewModel - .observe() - .subscribe { sharedAction -> - when (sharedAction) { - UserDirectorySharedAction.OpenUsersDirectory -> - addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) - UserDirectorySharedAction.Close -> finish() - UserDirectorySharedAction.GoBack -> onBackPressed() - is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() - }.exhaustive - } - .disposeOnDestroy() - if (isFirstCreation()) { - addFragment( - R.id.container, - KnownUsersFragment::class.java, - KnownUsersFragmentArgs( - title = getString(R.string.fab_menu_create_chat), - menuResId = R.menu.vector_create_direct_room, - isCreatingRoom = true - ) - ) + if (intent?.getBooleanExtra(BY_QR_CODE, false)!!) { + if (isFirstCreation()) { openAddByQrCode() } + } else { + sharedActionViewModel + .observe() + .subscribe { sharedAction -> + when (sharedAction) { + UserDirectorySharedAction.OpenUsersDirectory -> + addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) + UserDirectorySharedAction.Close -> finish() + UserDirectorySharedAction.GoBack -> onBackPressed() + is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) + UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() + }.exhaustive + } + .disposeOnDestroy() + if (isFirstCreation()) { + addFragment( + R.id.container, + KnownUsersFragment::class.java, + KnownUsersFragmentArgs( + title = getString(R.string.fab_menu_create_chat), + menuResId = R.menu.vector_create_direct_room, + isCreatingRoom = true + ) + ) + } } viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) { renderCreateAndInviteState(it) } } + private fun openAddByQrCode() { + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA, 0)) { + addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java) + } + } + private fun openPhoneBook() { // Check permission first if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, @@ -116,6 +129,13 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { if (allGranted(grantResults)) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } + } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) { + addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java) + } + } else { + Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() + if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) { + finish() } } } @@ -178,8 +198,12 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { } companion object { - fun getIntent(context: Context): Intent { - return Intent(context, CreateDirectRoomActivity::class.java) + private const val BY_QR_CODE = "BY_QR_CODE" + + fun getIntent(context: Context, byQrCode: Boolean = false): Intent { + return Intent(context, CreateDirectRoomActivity::class.java).apply { + putExtra(BY_QR_CODE, byQrCode) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt new file mode 100644 index 0000000000..f03368fdd5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.createdirect + +import android.widget.Toast +import com.airbnb.mvrx.activityViewModel +import com.google.zxing.Result +import com.google.zxing.ResultMetadataType +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.features.userdirectory.PendingInvitee +import kotlinx.android.synthetic.main.fragment_qr_code_scanner.* +import me.dm7.barcodescanner.zxing.ZXingScannerView +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.user.model.User +import javax.inject.Inject + +class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler { + + private val viewModel: CreateDirectRoomViewModel by activityViewModel() + + override fun getLayoutResId() = R.layout.fragment_qr_code_scanner + + override fun onResume() { + super.onResume() + // Register ourselves as a handler for scan results. + scannerView.setResultHandler(null) + // Start camera on resume + scannerView.startCamera() + } + + override fun onPause() { + super.onPause() + // Stop camera on pause + scannerView.stopCamera() + } + + // Copied from https://github.com/markusfisch/BinaryEye/blob/ + // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434 + private fun getRawBytes(result: Result): ByteArray? { + val metadata = result.resultMetadata ?: return null + val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null + var bytes = ByteArray(0) + @Suppress("UNCHECKED_CAST") + for (seg in segments as Iterable) { + bytes += seg + } + // byte segments can never be shorter than the text. + // Zxing cuts off content prefixes like "WIFI:" + return if (bytes.size >= result.text.length) bytes else null + } + + private fun addByQrCode(value: String) { + val mxid = (PermalinkParser.parse(value) as? PermalinkData.UserLink)?.userId + + if (mxid === null) { + Toast.makeText(requireContext(), R.string.invalid_qr_code_uri, Toast.LENGTH_SHORT).show() + requireActivity().finish() + } else { + val existingDm = viewModel.session.getExistingDirectRoomWithUser(mxid) + + if (existingDm === null) { + // The following assumes MXIDs are case insensitive + if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) { + Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show() + requireActivity().finish() + } else { + // Try to get user from known users and fall back to creating a User object from MXID + val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null) + + viewModel.handle( + CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee))) + ) + } + } else { + navigator.openRoom(requireContext(), existingDm, null, false) + requireActivity().finish() + } + } + } + + override fun handleResult(result: Result?) { + if (result === null) { + Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show() + requireActivity().finish() + } else { + val rawBytes = getRawBytes(result) + val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1) + val value = rawBytesStr ?: result.text + addByQrCode(value) + } + } +} 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 be9449b77a..d074c93587 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 @@ -38,7 +38,7 @@ import org.matrix.android.sdk.rx.rx class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, private val rawService: RawService, - private val session: Session) + val session: Session) : VectorViewModel(initialState) { @AssistedInject.Factory diff --git a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt index 0884844777..42bef2ddae 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt @@ -22,7 +22,7 @@ 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.room.list.widget.FabMenuView +import im.vector.app.features.home.room.list.widget.NotifsFabMenuView @EpoxyModelClass(layout = R.layout.item_room_filter_footer) abstract class FilteredRoomFooterItem : VectorEpoxyModel() { @@ -46,7 +46,7 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel(R.id.roomFilterFooterOpenRoomDirectory) } - interface FilteredRoomFooterItemListener : FabMenuView.Listener { + interface FilteredRoomFooterItemListener : NotifsFabMenuView.Listener { fun createRoom(initialName: String) } } 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 f1d35a74d5..e47072d0b0 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 @@ -45,7 +45,8 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel -import im.vector.app.features.home.room.list.widget.FabMenuView +import im.vector.app.features.home.room.list.widget.DmsFabMenuView +import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_list.* @@ -66,8 +67,7 @@ class RoomListFragment @Inject constructor( val roomListViewModelFactory: RoomListViewModel.Factory, private val notificationDrawerManager: NotificationDrawerManager, private val sharedViewPool: RecyclerView.RecycledViewPool - -) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener { +) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, DmsFabMenuView.Listener, NotifsFabMenuView.Listener { private var modelBuildListener: OnModelBuildFinishedListener? = null private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel @@ -111,6 +111,7 @@ class RoomListFragment @Inject constructor( }.exhaustive } + createDmFabMenu.listener = this createChatFabMenu.listener = this sharedActionViewModel @@ -129,6 +130,7 @@ class RoomListFragment @Inject constructor( roomListView.cleanup() roomController.listener = null stateRestorer.clear() + createDmFabMenu.listener = null createChatFabMenu.listener = null super.onDestroyView() } @@ -140,33 +142,32 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true + RoomListDisplayMode.PEOPLE -> createDmFabMenu.isVisible = true RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } - createChatRoomButton.debouncedClicks { - createDirectChat() - } createGroupRoomButton.debouncedClicks { openRoomDirectory() } - // Hide FAB when list is scrolling + // Hide FABs when list is scrolling roomListView.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + createDmFabMenu.removeCallbacks(showFabRunnable) createChatFabMenu.removeCallbacks(showFabRunnable) when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { + createDmFabMenu.postDelayed(showFabRunnable, 250) createChatFabMenu.postDelayed(showFabRunnable, 250) } RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_SETTLING -> { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide() - RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide() + RoomListDisplayMode.PEOPLE -> createDmFabMenu.hide() RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide() else -> Unit } @@ -191,6 +192,10 @@ class RoomListFragment @Inject constructor( navigator.openCreateDirectRoom(requireActivity()) } + override fun createDirectChatByQrCode() { + navigator.openCreateDirectRoom(requireContext(), true) + } + private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) stateRestorer = LayoutManagerStateRestorer(layoutManager).register() @@ -209,7 +214,7 @@ class RoomListFragment @Inject constructor( if (isAdded) { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> createChatRoomButton.show() + RoomListDisplayMode.PEOPLE -> createDmFabMenu.show() RoomListDisplayMode.ROOMS -> createGroupRoomButton.show() else -> Unit } @@ -338,6 +343,9 @@ class RoomListFragment @Inject constructor( } override fun onBackPressed(toolbarButton: Boolean): Boolean { + if (createDmFabMenu.onBackPressed()) { + return true + } if (createChatFabMenu.onBackPressed()) { return true } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt b/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt new file mode 100644 index 0000000000..9659b7b12b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt @@ -0,0 +1,102 @@ +/* + * 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.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.view.isVisible +import com.google.android.material.floatingactionbutton.FloatingActionButton +import im.vector.app.R +import kotlinx.android.synthetic.main.motion_dms_fab_menu_merge.view.* + +class DmsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, + defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) { + + var listener: Listener? = null + + init { + inflate(context, R.layout.motion_dms_fab_menu_merge, this) + } + + override fun onFinishInflate() { + super.onFinishInflate() + + listOf(createDmByMxid, createDmByMxidLabel) + .forEach { + it.setOnClickListener { + closeFabMenu() + listener?.createDirectChat() + } + } + listOf(createDmByQrCode, createDmByQrCodeLabel) + .forEach { + it.setOnClickListener { + closeFabMenu() + listener?.createDirectChatByQrCode() + } + } + + dmsCreateRoomTouchGuard.setOnClickListener { + closeFabMenu() + } + } + + override fun transitionToEnd() { + super.transitionToEnd() + + dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_close) + } + + override fun transitionToStart() { + super.transitionToStart() + + dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_open) + } + + fun show() { + isVisible = true + dmsCreateRoomButton.show() + } + + fun hide() { + dmsCreateRoomButton.hide(object : FloatingActionButton.OnVisibilityChangedListener() { + override fun onHidden(fab: FloatingActionButton?) { + super.onHidden(fab) + isVisible = false + } + }) + } + + private fun closeFabMenu() { + transitionToStart() + } + + fun onBackPressed(): Boolean { + if (currentState == R.id.constraint_set_fab_menu_open) { + closeFabMenu() + return true + } + + return false + } + + interface Listener { + fun createDirectChat() + fun createDirectChatByQrCode() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/widget/FabMenuView.kt b/vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt similarity index 88% rename from vector/src/main/java/im/vector/app/features/home/room/list/widget/FabMenuView.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt index f9058840d2..7c96f40dbf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/widget/FabMenuView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/widget/NotifsFabMenuView.kt @@ -22,15 +22,15 @@ import androidx.constraintlayout.motion.widget.MotionLayout import androidx.core.view.isVisible import com.google.android.material.floatingactionbutton.FloatingActionButton import im.vector.app.R -import kotlinx.android.synthetic.main.motion_fab_menu_merge.view.* +import kotlinx.android.synthetic.main.motion_notifs_fab_menu_merge.view.* -class FabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) { +class NotifsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, + defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) { var listener: Listener? = null init { - inflate(context, R.layout.motion_fab_menu_merge, this) + inflate(context, R.layout.motion_notifs_fab_menu_merge, this) } override fun onFinishInflate() { 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 2d0ca86d52..9ff103113f 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 @@ -203,8 +203,8 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } - override fun openCreateDirectRoom(context: Context) { - val intent = CreateDirectRoomActivity.getIntent(context) + override fun openCreateDirectRoom(context: Context, byQrCode: Boolean) { + val intent = CreateDirectRoomActivity.getIntent(context, byQrCode) 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 504fccb63a..23d24b709c 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 @@ -56,7 +56,7 @@ interface Navigator { fun openCreateRoom(context: Context, initialName: String = "") - fun openCreateDirectRoom(context: Context) + fun openCreateDirectRoom(context: Context, byQrCode: Boolean = false) fun openInviteUsersToRoom(context: Context, roomId: String) diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt index 8f57acee47..7b2e329b6a 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryItem.kt @@ -62,7 +62,7 @@ abstract class RoomDirectoryItem : VectorEpoxyModel() holder.avatarView.isInvisible = directoryAvatarUrl.isNullOrBlank() && includeAllNetworks holder.nameView.text = directoryName - holder.descritionView.setTextOrHide(directoryDescription) + holder.descriptionView.setTextOrHide(directoryDescription) } class Holder : VectorEpoxyHolder() { @@ -70,6 +70,6 @@ abstract class RoomDirectoryItem : VectorEpoxyModel() val avatarView by bind(R.id.itemRoomDirectoryAvatar) val nameView by bind(R.id.itemRoomDirectoryName) - val descritionView by bind(R.id.itemRoomDirectoryDescription) + val descriptionView by bind(R.id.itemRoomDirectoryDescription) } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt index d60b5580fa..e994a3c3ec 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt @@ -294,12 +294,20 @@ class RoomMemberProfileFragment @Inject constructor( } private fun handleShareRoomMemberProfile(permalink: String) { - startSharePlainTextIntent( - fragment = this, - activityResultLauncher = null, - chooserTitle = null, - text = permalink - ) + val view = layoutInflater.inflate(R.layout.dialog_share_qr_code, null) + val qrCode = view.findViewById(R.id.itemShareQrCodeImage) + qrCode.setData(permalink) + AlertDialog.Builder(requireContext()) + .setView(view) + .setNeutralButton(R.string.ok, null) + .setPositiveButton(R.string.share_by_text) { _, _ -> + startSharePlainTextIntent( + fragment = this, + activityResultLauncher = null, + chooserTitle = null, + text = permalink + ) + }.show() } private fun onAvatarClicked(view: View, userMatrixItem: MatrixItem) { diff --git a/vector/src/main/res/drawable/ic_fab_add_by_mxid.xml b/vector/src/main/res/drawable/ic_fab_add_by_mxid.xml new file mode 100644 index 0000000000..50768871ab --- /dev/null +++ b/vector/src/main/res/drawable/ic_fab_add_by_mxid.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml b/vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml new file mode 100644 index 0000000000..50768871ab --- /dev/null +++ b/vector/src/main/res/drawable/ic_fab_add_by_qr_code.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/vector/src/main/res/layout/dialog_share_qr_code.xml b/vector/src/main/res/layout/dialog_share_qr_code.xml new file mode 100644 index 0000000000..d209376edb --- /dev/null +++ b/vector/src/main/res/layout/dialog_share_qr_code.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index f0bc336cbd..855c45f7c5 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -14,29 +14,25 @@ android:overScrollMode="always" tools:listitem="@layout/item_room" /> - - + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/motion_fab_menu_merge.xml b/vector/src/main/res/layout/motion_notifs_fab_menu_merge.xml similarity index 98% rename from vector/src/main/res/layout/motion_fab_menu_merge.xml rename to vector/src/main/res/layout/motion_notifs_fab_menu_merge.xml index e564d16aaf..8130ea0637 100644 --- a/vector/src/main/res/layout/motion_fab_menu_merge.xml +++ b/vector/src/main/res/layout/motion_notifs_fab_menu_merge.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - app:layoutDescription="@xml/motion_scene_fab_menu" + app:layoutDescription="@xml/motion_scene_notifs_fab_menu" tools:motionProgress="0.65" tools:parentTag="androidx.constraintlayout.motion.widget.MotionLayout" tools:showPaths="true"> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index cbfb95dc85..1eb602e4c3 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1761,6 +1761,7 @@ Link copied to clipboard Add by matrix ID + Add by QR code "Creating room…" "No result found, use Add by matrix ID to search on server." "Start typing to get results" @@ -1828,6 +1829,8 @@ Open the create room menu Close the create room menu… Create a new direct conversation + Create a new direct conversation by Matrix ID + Create a new direct conversation by scanning a QR code Create a new room Close keys backup banner Show password @@ -2674,4 +2677,10 @@ The room is not yet created. Cancel the room creation? There are unsaved changes. Discard the changes? Discard changes + + + Share by text + Cannot DM yourself! + Invalid QR code (Invalid URI)! + QR code not scanned! diff --git a/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml b/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml new file mode 100644 index 0000000000..8bb1c55df5 --- /dev/null +++ b/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/xml/motion_scene_fab_menu.xml b/vector/src/main/res/xml/motion_scene_notifs_fab_menu.xml similarity index 100% rename from vector/src/main/res/xml/motion_scene_fab_menu.xml rename to vector/src/main/res/xml/motion_scene_notifs_fab_menu.xml From 1070c23608240e1780be7a2a6a48bcb01e86d092 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Nov 2020 13:11:41 +0100 Subject: [PATCH 120/231] QR code invite flow support - invite friends Author: Valere --- CHANGES.md | 3 + .../android/sdk/api/auth/login/LoginWizard.kt | 2 +- vector/src/main/AndroidManifest.xml | 1 + .../im/vector/app/core/di/FragmentModule.kt | 18 +- .../im/vector/app/core/di/ScreenComponent.kt | 4 + .../im/vector/app/core/di/ViewModelModule.kt | 6 +- .../im/vector/app/core/epoxy/CheckBoxItem.kt | 47 ++++ .../im/vector/app/core/extensions/EditText.kt | 2 +- .../im/vector/app/core/utils/SystemUtils.kt | 8 +- .../contactsbook/ContactsBookFragment.kt | 24 +-- .../createdirect/CreateDirectRoomActivity.kt | 88 ++++---- .../CreateDirectRoomByQrCodeFragment.kt | 54 +++-- .../app/features/home/HomeDrawerFragment.kt | 30 +++ .../home/HomeSharedActionViewModel.kt | 3 +- .../home/room/list/RoomListFragment.kt | 25 +-- .../home/room/list/widget/DmsFabMenuView.kt | 102 --------- .../HomeServerCapabilitiesViewModel.kt | 4 +- .../invite/InviteUsersToRoomActivity.kt | 64 +++--- .../features/matrixto/MatrixToBottomSheet.kt | 67 ++++++ .../features/navigation/DefaultNavigator.kt | 4 +- .../app/features/navigation/Navigator.kt | 2 +- .../RoomMemberProfileController.kt | 11 + .../usercode/QRCodeBitmapDecodeHelper.kt | 85 ++++++++ .../features/usercode/ScanUserCodeFragment.kt | 135 ++++++++++++ .../features/usercode/ShowUserCodeFragment.kt | 59 +++++ .../app/features/usercode/UserCodeActions.kt | 27 +++ .../app/features/usercode/UserCodeActivity.kt | 126 +++++++++++ .../usercode/UserCodeShareViewEvents.kt | 28 +++ .../usercode/UserCodeSharedViewModel.kt | 161 ++++++++++++++ .../app/features/usercode/UserCodeState.kt | 33 +++ .../app/features/userdirectory/ActionItem.kt | 53 +++++ .../userdirectory/ContactDetailItem.kt | 47 ++++ .../app/features/userdirectory/ContactItem.kt | 46 ++++ .../userdirectory/DirectoryUsersController.kt | 139 ------------ .../userdirectory/KnownUsersController.kt | 122 ----------- .../userdirectory/UserDirectoryFragment.kt | 94 -------- .../userdirectory/UserDirectoryViewModel.kt | 153 ------------- ...erDirectoryAction.kt => UserListAction.kt} | 12 +- .../userdirectory/UserListController.kt | 197 +++++++++++++++++ ...wnUsersFragment.kt => UserListFragment.kt} | 133 +++++++----- ...ragmentArgs.kt => UserListFragmentArgs.kt} | 4 +- .../userdirectory/UserListHeaderItem.kt | 39 ++++ ...haredAction.kt => UserListSharedAction.kt} | 14 +- ...el.kt => UserListSharedActionViewModel.kt} | 2 +- ...oryViewEvents.kt => UserListViewEvents.kt} | 4 +- .../userdirectory/UserListViewModel.kt | 204 ++++++++++++++++++ ...ctoryViewState.kt => UserListViewState.kt} | 15 +- vector/src/main/res/drawable/ic_book.xml | 21 ++ .../main/res/drawable/ic_invite_people.xml | 10 + .../src/main/res/drawable/ic_picture_icon.xml | 29 +++ .../src/main/res/drawable/ic_qr_code_add.xml | 72 +++++++ .../src/main/res/layout/activity_simple.xml | 24 ++- .../main/res/layout/fragment_home_drawer.xml | 70 +++++- .../res/layout/fragment_matrix_to_card.xml | 70 ++++++ .../res/layout/fragment_qr_code_scanner.xml | 24 +++ .../fragment_qr_code_scanner_with_button.xml | 51 +++++ .../main/res/layout/fragment_room_list.xml | 16 +- .../res/layout/fragment_user_code_show.xml | 183 ++++++++++++++++ ...known_users.xml => fragment_user_list.xml} | 65 ++---- vector/src/main/res/layout/item_checkbox.xml | 13 ++ .../main/res/layout/item_contact_action.xml | 31 +++ .../main/res/layout/item_user_list_header.xml | 14 ++ .../res/layout/motion_dms_fab_menu_merge.xml | 79 ------- vector/src/main/res/values/strings.xml | 27 ++- .../res/xml/motion_scene_dms_fab_menu.xml | 199 ----------------- 65 files changed, 2311 insertions(+), 1188 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt create mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt create mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt delete mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt delete mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt delete mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt delete mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt rename vector/src/main/java/im/vector/app/features/userdirectory/{UserDirectoryAction.kt => UserListAction.kt} (71%) create mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt rename vector/src/main/java/im/vector/app/features/userdirectory/{KnownUsersFragment.kt => UserListFragment.kt} (57%) rename vector/src/main/java/im/vector/app/features/userdirectory/{KnownUsersFragmentArgs.kt => UserListFragmentArgs.kt} (91%) create mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt rename vector/src/main/java/im/vector/app/features/userdirectory/{UserDirectorySharedAction.kt => UserListSharedAction.kt} (59%) rename vector/src/main/java/im/vector/app/features/userdirectory/{UserDirectorySharedActionViewModel.kt => UserListSharedActionViewModel.kt} (85%) rename vector/src/main/java/im/vector/app/features/userdirectory/{UserDirectoryViewEvents.kt => UserListViewEvents.kt} (85%) create mode 100644 vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt rename vector/src/main/java/im/vector/app/features/userdirectory/{UserDirectoryViewState.kt => UserListViewState.kt} (78%) create mode 100644 vector/src/main/res/drawable/ic_book.xml create mode 100644 vector/src/main/res/drawable/ic_invite_people.xml create mode 100644 vector/src/main/res/drawable/ic_picture_icon.xml create mode 100644 vector/src/main/res/drawable/ic_qr_code_add.xml create mode 100644 vector/src/main/res/layout/fragment_matrix_to_card.xml create mode 100644 vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml create mode 100644 vector/src/main/res/layout/fragment_user_code_show.xml rename vector/src/main/res/layout/{fragment_known_users.xml => fragment_user_list.xml} (68%) create mode 100644 vector/src/main/res/layout/item_checkbox.xml create mode 100644 vector/src/main/res/layout/item_contact_action.xml create mode 100644 vector/src/main/res/layout/item_user_list_header.xml delete mode 100644 vector/src/main/res/layout/motion_dms_fab_menu_merge.xml delete mode 100644 vector/src/main/res/xml/motion_scene_dms_fab_menu.xml diff --git a/CHANGES.md b/CHANGES.md index 4873a999b6..e1731433c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,8 @@ Changes in Element 1.0.11 (2020-XX-XX) Features ✨: - Create DMs with users by scanning their QR code (#2025) + - Add Invite friends quick invite actions (#2348) + - Add friend by scanning QR code, show your code to friends (#2025) Improvements 🙌: - New room creation tile with quick action (#2346) @@ -12,6 +14,7 @@ Improvements 🙌: - Handle events of type "m.room.server_acl" (#890) - Room creation form: add advanced section to disable federation (#1314) - Move "Enable Encryption" from room setting screen to room profile screen (#2394) + - Improve Invite user screen (seamless search for matrix ID) Bugfix 🐛: - Fix crash on AttachmentViewer (#2365) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt index 645fb55bb9..48705ee7b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt @@ -27,7 +27,7 @@ interface LoginWizard { * @param password the password field * @param deviceName the initial device name * @param callback the matrix callback on which you'll receive the result of authentication. - * @return return a [Cancelable] + * @return a [Cancelable] */ fun login(login: String, password: String, diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index fb4764b3be..7d2ca11813 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -229,6 +229,7 @@ + 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 acdad5407c..32c98922fb 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 @@ -111,8 +111,8 @@ import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment import im.vector.app.features.share.IncomingShareFragment import im.vector.app.features.signout.soft.SoftLogoutFragment import im.vector.app.features.terms.ReviewTermsFragment -import im.vector.app.features.userdirectory.KnownUsersFragment -import im.vector.app.features.userdirectory.UserDirectoryFragment +import im.vector.app.features.usercode.ShowUserCodeFragment +import im.vector.app.features.userdirectory.UserListFragment import im.vector.app.features.widgets.WidgetFragment @Module @@ -255,13 +255,8 @@ interface FragmentModule { @Binds @IntoMap - @FragmentKey(UserDirectoryFragment::class) - fun bindUserDirectoryFragment(fragment: UserDirectoryFragment): Fragment - - @Binds - @IntoMap - @FragmentKey(KnownUsersFragment::class) - fun bindKnownUsersFragment(fragment: KnownUsersFragment): Fragment + @FragmentKey(UserListFragment::class) + fun bindUserListFragment(fragment: UserListFragment): Fragment @Binds @IntoMap @@ -582,4 +577,9 @@ interface FragmentModule { @IntoMap @FragmentKey(SearchFragment::class) fun bindSearchFragment(fragment: SearchFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(ShowUserCodeFragment::class) + fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment } 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 fde40f9195..818a32fca3 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 @@ -50,6 +50,7 @@ import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.link.LinkHandlerActivity import im.vector.app.features.login.LoginActivity +import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.media.BigImageViewerActivity import im.vector.app.features.media.VectorAttachmentViewerActivity import im.vector.app.features.navigation.Navigator @@ -72,6 +73,7 @@ import im.vector.app.features.share.IncomingShareActivity import im.vector.app.features.signout.soft.SoftLogoutActivity import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.ui.UiStateRepository +import im.vector.app.features.usercode.UserCodeActivity import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment @@ -140,6 +142,7 @@ interface ScreenComponent { fun inject(activity: VectorAttachmentViewerActivity) fun inject(activity: VectorJitsiActivity) fun inject(activity: SearchActivity) + fun inject(activity: UserCodeActivity) /* ========================================================================================== * BottomSheets @@ -158,6 +161,7 @@ interface ScreenComponent { fun inject(bottomSheet: RoomWidgetsBottomSheet) fun inject(bottomSheet: CallControlsBottomSheet) fun inject(bottomSheet: SignOutBottomSheetDialogFragment) + fun inject(bottomSheet: MatrixToBottomSheet) /* ========================================================================================== * Others diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 836dab00c5..7ae8bc9c2e 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -35,7 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.reactions.EmojiChooserViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel -import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel +import im.vector.app.features.userdirectory.UserListSharedActionViewModel @Module interface ViewModelModule { @@ -87,8 +87,8 @@ interface ViewModelModule { @Binds @IntoMap - @ViewModelKey(UserDirectorySharedActionViewModel::class) - fun bindUserDirectorySharedActionViewModel(viewModel: UserDirectorySharedActionViewModel): ViewModel + @ViewModelKey(UserListSharedActionViewModel::class) + fun bindUserListSharedActionViewModel(viewModel: UserListSharedActionViewModel): ViewModel @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt new file mode 100644 index 0000000000..4e53b293d3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.epoxy + +import android.widget.CompoundButton +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.checkbox.MaterialCheckBox +import im.vector.app.R +import kotlinx.android.synthetic.main.vector_preference_push_rule.view.* + +@EpoxyModelClass(layout = R.layout.item_checkbox) +abstract class CheckBoxItem : VectorEpoxyModel() { + + @EpoxyAttribute + var checked: Boolean = false + + @EpoxyAttribute lateinit var title: String + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.checkbox.isChecked = checked + holder.checkbox.text = title + holder.checkbox.setOnCheckedChangeListener(checkChangeListener) + } + + class Holder : VectorEpoxyHolder() { + val checkbox by bind(R.id.checkbox) + } +} diff --git a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt index 355dd8442f..05b70def3d 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt @@ -26,7 +26,7 @@ import androidx.annotation.DrawableRes import im.vector.app.R import im.vector.app.core.platform.SimpleTextWatcher -fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter, +fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_search, @DrawableRes clearIconRes: Int = R.drawable.ic_x_gray) { addTextChangedListener(object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt index d228adab12..2348b07c7b 100644 --- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt @@ -136,13 +136,19 @@ fun startSharePlainTextIntent(fragment: Fragment, activityResultLauncher: ActivityResultLauncher?, chooserTitle: String?, text: String, - subject: String? = null) { + subject: String? = null, + extraTitle: String? = null) { val share = Intent(Intent.ACTION_SEND) share.type = "text/plain" share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) // Add data to the intent, the receiving app will decide what to do with it. share.putExtra(Intent.EXTRA_SUBJECT, subject) share.putExtra(Intent.EXTRA_TEXT, text) + + extraTitle?.let { + share.putExtra(Intent.EXTRA_TITLE, it) + } + val intent = Intent.createChooser(share, chooserTitle) try { if (activityResultLauncher != null) { diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt index 23d21f5240..6c3ec06f75 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt @@ -30,10 +30,10 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.features.userdirectory.PendingInvitee -import im.vector.app.features.userdirectory.UserDirectoryAction -import im.vector.app.features.userdirectory.UserDirectorySharedAction -import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel -import im.vector.app.features.userdirectory.UserDirectoryViewModel +import im.vector.app.features.userdirectory.UserListAction +import im.vector.app.features.userdirectory.UserListSharedAction +import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import im.vector.app.features.userdirectory.UserListViewModel import kotlinx.android.synthetic.main.fragment_contacts_book.* import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.user.model.User @@ -46,16 +46,16 @@ class ContactsBookFragment @Inject constructor( ) : VectorBaseFragment(), ContactsBookController.Callback { override fun getLayoutResId() = R.layout.fragment_contacts_book - private val viewModel: UserDirectoryViewModel by activityViewModel() + private val viewModel: UserListViewModel by activityViewModel() // Use activityViewModel to avoid loading several times the data private val contactsBookViewModel: ContactsBookViewModel by activityViewModel() - private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel + private lateinit var sharedActionViewModel: UserListSharedActionViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java) setupRecyclerView() setupFilterView() setupConsentView() @@ -110,7 +110,7 @@ class ContactsBookFragment @Inject constructor( private fun setupCloseView() { phoneBookClose.debouncedClicks { - sharedActionViewModel.post(UserDirectorySharedAction.GoBack) + sharedActionViewModel.post(UserListSharedAction.GoBack) } } @@ -122,13 +122,13 @@ class ContactsBookFragment @Inject constructor( override fun onMatrixIdClick(matrixId: String) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) - sharedActionViewModel.post(UserDirectorySharedAction.GoBack) + viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) + sharedActionViewModel.post(UserListSharedAction.GoBack) } override fun onThreePidClick(threePid: ThreePid) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) - sharedActionViewModel.post(UserDirectorySharedAction.GoBack) + viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) + sharedActionViewModel.post(UserListSharedAction.GoBack) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 2035ee50f6..2e21d04d06 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -45,23 +45,23 @@ import im.vector.app.core.utils.allGranted import im.vector.app.core.utils.checkPermissions import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel -import im.vector.app.features.userdirectory.KnownUsersFragment -import im.vector.app.features.userdirectory.KnownUsersFragmentArgs -import im.vector.app.features.userdirectory.UserDirectoryFragment -import im.vector.app.features.userdirectory.UserDirectorySharedAction -import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel -import im.vector.app.features.userdirectory.UserDirectoryViewModel +import im.vector.app.features.userdirectory.UserListFragment +import im.vector.app.features.userdirectory.UserListFragmentArgs +import im.vector.app.features.userdirectory.UserListSharedAction +import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import im.vector.app.features.userdirectory.UserListViewModel +import im.vector.app.features.userdirectory.UserListViewState import kotlinx.android.synthetic.main.activity.* import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import java.net.HttpURLConnection import javax.inject.Inject -class CreateDirectRoomActivity : SimpleFragmentActivity() { +class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory { private val viewModel: CreateDirectRoomViewModel by viewModel() - private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel - @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory + private lateinit var sharedActionViewModel: UserListSharedActionViewModel + @Inject lateinit var userListViewModelFactory: UserListViewModel.Factory @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter @@ -71,37 +71,36 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { injector.inject(this) } + override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel { + return userListViewModelFactory.create(initialState, args) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) toolbar.visibility = View.GONE - sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java) - if (intent?.getBooleanExtra(BY_QR_CODE, false)!!) { - if (isFirstCreation()) { openAddByQrCode() } - } else { - sharedActionViewModel - .observe() - .subscribe { sharedAction -> - when (sharedAction) { - UserDirectorySharedAction.OpenUsersDirectory -> - addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) - UserDirectorySharedAction.Close -> finish() - UserDirectorySharedAction.GoBack -> onBackPressed() - is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() - }.exhaustive - } - .disposeOnDestroy() - if (isFirstCreation()) { - addFragment( - R.id.container, - KnownUsersFragment::class.java, - KnownUsersFragmentArgs( - title = getString(R.string.fab_menu_create_chat), - menuResId = R.menu.vector_create_direct_room, - isCreatingRoom = true - ) - ) - } + + sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) + sharedActionViewModel + .observe() + .subscribe { action -> + when (action) { + UserListSharedAction.Close -> finish() + UserListSharedAction.GoBack -> onBackPressed() + is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action) + UserListSharedAction.OpenPhoneBook -> openPhoneBook() + UserListSharedAction.AddByQrCode -> openAddByQrCode() + }.exhaustive + } + .disposeOnDestroy() + if (isFirstCreation()) { + addFragment( + R.id.container, + UserListFragment::class.java, + UserListFragmentArgs( + title = getString(R.string.fab_menu_create_chat), + menuResId = R.menu.vector_create_direct_room + ) + ) } viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) { renderCreateAndInviteState(it) @@ -129,22 +128,22 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { if (allGranted(grantResults)) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } - } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) { + } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java) } } else { Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() - if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) { + if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { finish() } } } - private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { + private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers( action.invitees, - action.existingDmRoomId + null )) } } @@ -198,12 +197,9 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { } companion object { - private const val BY_QR_CODE = "BY_QR_CODE" - fun getIntent(context: Context, byQrCode: Boolean = false): Intent { - return Intent(context, CreateDirectRoomActivity::class.java).apply { - putExtra(BY_QR_CODE, byQrCode) - } + fun getIntent(context: Context): Intent { + return Intent(context, CreateDirectRoomActivity::class.java) } } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt index f03368fdd5..3fee3a3285 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt @@ -21,7 +21,11 @@ import com.airbnb.mvrx.activityViewModel import com.google.zxing.Result import com.google.zxing.ResultMetadataType import im.vector.app.R +import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.checkPermissions +import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.features.userdirectory.PendingInvitee import kotlinx.android.synthetic.main.fragment_qr_code_scanner.* import me.dm7.barcodescanner.zxing.ZXingScannerView @@ -36,16 +40,32 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen override fun getLayoutResId() = R.layout.fragment_qr_code_scanner - override fun onResume() { - super.onResume() - // Register ourselves as a handler for scan results. - scannerView.setResultHandler(null) + private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + startCamera() + } + } + + private fun startCamera() { // Start camera on resume scannerView.startCamera() } + override fun onResume() { + super.onResume() + view?.hideKeyboard() + // Register ourselves as a handler for scan results. + scannerView.setResultHandler(this) + // Start camera on resume + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + startCamera() + } + } + override fun onPause() { super.onPause() + // Unregister ourselves as a handler for scan results. + scannerView.setResultHandler(null) // Stop camera on pause scannerView.stopCamera() } @@ -73,23 +93,17 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen requireActivity().finish() } else { val existingDm = viewModel.session.getExistingDirectRoomWithUser(mxid) - - if (existingDm === null) { - // The following assumes MXIDs are case insensitive - if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) { - Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show() - requireActivity().finish() - } else { - // Try to get user from known users and fall back to creating a User object from MXID - val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null) - - viewModel.handle( - CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee))) - ) - } - } else { - navigator.openRoom(requireContext(), existingDm, null, false) + // The following assumes MXIDs are case insensitive + if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) { + Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show() requireActivity().finish() + } else { + // Try to get user from known users and fall back to creating a User object from MXID + val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null) + + viewModel.handle( + CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)), existingDm) + ) } } } 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 e267248fc3..1a60d8e219 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 @@ -18,15 +18,19 @@ package im.vector.app.features.home import android.os.Bundle import android.view.View +import androidx.core.app.ActivityOptionsCompat +import androidx.core.view.ViewCompat import androidx.core.view.isVisible import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.extensions.observeK import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.features.grouplist.GroupListFragment import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity +import im.vector.app.features.usercode.UserCodeActivity import im.vector.app.features.workers.signout.SignOutUiWorker import kotlinx.android.synthetic.main.fragment_home_drawer.* import org.matrix.android.sdk.api.session.Session @@ -75,6 +79,32 @@ class HomeDrawerFragment @Inject constructor( SignOutUiWorker(requireActivity()).perform() } + homeDrawerQRCodeButton.debouncedClicks { + UserCodeActivity.newIntent(requireContext(), sharedActionViewModel.session.myUserId).let { + val options = + ActivityOptionsCompat.makeSceneTransitionAnimation( + requireActivity(), + homeDrawerHeaderAvatarView, + ViewCompat.getTransitionName(homeDrawerHeaderAvatarView) ?: "" + ) + startActivity(it, options.toBundle()) + } + } + + homeDrawerInviteFriendButton.debouncedClicks { + session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink -> + val text = getString(R.string.invite_friends_text, permalink) + + startSharePlainTextIntent( + fragment = this, + activityResultLauncher = null, + chooserTitle = getString(R.string.invite_friends), + text = text, + extraTitle = getString(R.string.invite_friends_rich_title) + ) + } + } + // Debug menu homeDrawerHeaderDebugView.isVisible = BuildConfig.DEBUG && vectorPreferences.developerMode() homeDrawerHeaderDebugView.debouncedClicks { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt index 58747a4c18..b695f48ee5 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home import im.vector.app.core.platform.VectorSharedActionViewModel +import org.matrix.android.sdk.api.session.Session import javax.inject.Inject -class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() +class HomeSharedActionViewModel @Inject constructor(val session: Session) : VectorSharedActionViewModel() 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 e47072d0b0..60c2745b44 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 @@ -45,7 +45,6 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel -import im.vector.app.features.home.room.list.widget.DmsFabMenuView import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager import kotlinx.android.parcel.Parcelize @@ -67,7 +66,7 @@ class RoomListFragment @Inject constructor( val roomListViewModelFactory: RoomListViewModel.Factory, private val notificationDrawerManager: NotificationDrawerManager, private val sharedViewPool: RecyclerView.RecycledViewPool -) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, DmsFabMenuView.Listener, NotifsFabMenuView.Listener { +) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, NotifsFabMenuView.Listener { private var modelBuildListener: OnModelBuildFinishedListener? = null private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel @@ -111,7 +110,6 @@ class RoomListFragment @Inject constructor( }.exhaustive } - createDmFabMenu.listener = this createChatFabMenu.listener = this sharedActionViewModel @@ -130,7 +128,6 @@ class RoomListFragment @Inject constructor( roomListView.cleanup() roomController.listener = null stateRestorer.clear() - createDmFabMenu.listener = null createChatFabMenu.listener = null super.onDestroyView() } @@ -142,32 +139,33 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> createDmFabMenu.isVisible = true + RoomListDisplayMode.PEOPLE -> createChatRoomButton.isVisible = true RoomListDisplayMode.ROOMS -> createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } + createChatRoomButton.debouncedClicks { + createDirectChat() + } createGroupRoomButton.debouncedClicks { openRoomDirectory() } - // Hide FABs when list is scrolling + // Hide FAB when list is scrolling roomListView.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { - createDmFabMenu.removeCallbacks(showFabRunnable) createChatFabMenu.removeCallbacks(showFabRunnable) when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { - createDmFabMenu.postDelayed(showFabRunnable, 250) createChatFabMenu.postDelayed(showFabRunnable, 250) } RecyclerView.SCROLL_STATE_DRAGGING, RecyclerView.SCROLL_STATE_SETTLING -> { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide() - RoomListDisplayMode.PEOPLE -> createDmFabMenu.hide() + RoomListDisplayMode.PEOPLE -> createChatRoomButton.hide() RoomListDisplayMode.ROOMS -> createGroupRoomButton.hide() else -> Unit } @@ -192,10 +190,6 @@ class RoomListFragment @Inject constructor( navigator.openCreateDirectRoom(requireActivity()) } - override fun createDirectChatByQrCode() { - navigator.openCreateDirectRoom(requireContext(), true) - } - private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) stateRestorer = LayoutManagerStateRestorer(layoutManager).register() @@ -214,7 +208,7 @@ class RoomListFragment @Inject constructor( if (isAdded) { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> createDmFabMenu.show() + RoomListDisplayMode.PEOPLE -> createChatRoomButton.show() RoomListDisplayMode.ROOMS -> createGroupRoomButton.show() else -> Unit } @@ -343,9 +337,6 @@ class RoomListFragment @Inject constructor( } override fun onBackPressed(toolbarButton: Boolean): Boolean { - if (createDmFabMenu.onBackPressed()) { - return true - } if (createChatFabMenu.onBackPressed()) { return true } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt b/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt deleted file mode 100644 index 9659b7b12b..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.home.room.list.widget - -import android.content.Context -import android.util.AttributeSet -import androidx.constraintlayout.motion.widget.MotionLayout -import androidx.core.view.isVisible -import com.google.android.material.floatingactionbutton.FloatingActionButton -import im.vector.app.R -import kotlinx.android.synthetic.main.motion_dms_fab_menu_merge.view.* - -class DmsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, - defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) { - - var listener: Listener? = null - - init { - inflate(context, R.layout.motion_dms_fab_menu_merge, this) - } - - override fun onFinishInflate() { - super.onFinishInflate() - - listOf(createDmByMxid, createDmByMxidLabel) - .forEach { - it.setOnClickListener { - closeFabMenu() - listener?.createDirectChat() - } - } - listOf(createDmByQrCode, createDmByQrCodeLabel) - .forEach { - it.setOnClickListener { - closeFabMenu() - listener?.createDirectChatByQrCode() - } - } - - dmsCreateRoomTouchGuard.setOnClickListener { - closeFabMenu() - } - } - - override fun transitionToEnd() { - super.transitionToEnd() - - dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_close) - } - - override fun transitionToStart() { - super.transitionToStart() - - dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_open) - } - - fun show() { - isVisible = true - dmsCreateRoomButton.show() - } - - fun hide() { - dmsCreateRoomButton.hide(object : FloatingActionButton.OnVisibilityChangedListener() { - override fun onHidden(fab: FloatingActionButton?) { - super.onHidden(fab) - isVisible = false - } - }) - } - - private fun closeFabMenu() { - transitionToStart() - } - - fun onBackPressed(): Boolean { - if (currentState == R.id.constraint_set_fab_menu_open) { - closeFabMenu() - return true - } - - return false - } - - interface Listener { - fun createDirectChat() - fun createDirectChatByQrCode() - } -} diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt index f6c60f6a6d..fe7a8006e0 100644 --- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt @@ -28,7 +28,7 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.raw.wellknown.isE2EByDefault -import im.vector.app.features.userdirectory.KnownUsersFragment +import im.vector.app.features.userdirectory.UserListFragment import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull @@ -50,7 +50,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor( companion object : MvRxViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel? { - val fragment: KnownUsersFragment = (viewModelContext as FragmentViewModelContext).fragment() + val fragment: UserListFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.homeServerCapabilitiesViewModelFactory.create(state) } diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 087b7c2f55..513fbb5d83 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.View +import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel @@ -29,7 +30,6 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.addFragment import im.vector.app.core.extensions.addFragmentToBackstack -import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.SimpleFragmentActivity import im.vector.app.core.platform.WaitingViewData import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH @@ -39,12 +39,12 @@ import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.toast import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel -import im.vector.app.features.userdirectory.KnownUsersFragment -import im.vector.app.features.userdirectory.KnownUsersFragmentArgs -import im.vector.app.features.userdirectory.UserDirectoryFragment -import im.vector.app.features.userdirectory.UserDirectorySharedAction -import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel -import im.vector.app.features.userdirectory.UserDirectoryViewModel +import im.vector.app.features.userdirectory.UserListFragment +import im.vector.app.features.userdirectory.UserListFragmentArgs +import im.vector.app.features.userdirectory.UserListSharedAction +import im.vector.app.features.userdirectory.UserListSharedActionViewModel +import im.vector.app.features.userdirectory.UserListViewModel +import im.vector.app.features.userdirectory.UserListViewState import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity.* import org.matrix.android.sdk.api.failure.Failure @@ -54,11 +54,11 @@ import javax.inject.Inject @Parcelize data class InviteUsersToRoomArgs(val roomId: String) : Parcelable -class InviteUsersToRoomActivity : SimpleFragmentActivity() { +class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory { private val viewModel: InviteUsersToRoomViewModel by viewModel() - private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel - @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory + private lateinit var sharedActionViewModel: UserListSharedActionViewModel + @Inject lateinit var userListViewModelFactory: UserListViewModel.Factory @Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter @@ -68,32 +68,40 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { injector.inject(this) } + override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel { + return userListViewModelFactory.create(initialState, args) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) toolbar.visibility = View.GONE - sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + + sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java) sharedActionViewModel .observe() .subscribe { sharedAction -> when (sharedAction) { - UserDirectorySharedAction.OpenUsersDirectory -> - addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) - UserDirectorySharedAction.Close -> finish() - UserDirectorySharedAction.GoBack -> onBackPressed() - is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() - }.exhaustive + UserListSharedAction.Close -> finish() + UserListSharedAction.GoBack -> onBackPressed() + is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) + UserListSharedAction.OpenPhoneBook -> openPhoneBook() + // not exhaustive because it's a sharedAction + else -> { + } + } } .disposeOnDestroy() if (isFirstCreation()) { + val args: InviteUsersToRoomArgs? = intent.extras?.getParcelable(MvRx.KEY_ARG) addFragment( R.id.container, - KnownUsersFragment::class.java, - KnownUsersFragmentArgs( + UserListFragment::class.java, + UserListFragmentArgs( title = getString(R.string.invite_users_to_room_title), menuResId = R.menu.vector_invite_users_to_room, - excludedUserIds = viewModel.getUserIdsOfRoomMembers() + excludedUserIds = viewModel.getUserIdsOfRoomMembers(), + existingRoomId = args?.roomId ) ) } @@ -101,6 +109,12 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { viewModel.observeViewEvents { renderInviteEvents(it) } } + private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { + if (action.itemId == R.id.action_invite_users_to_room_invite) { + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees)) + } + } + private fun openPhoneBook() { // Check permission first if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, @@ -117,12 +131,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } } - } - } - - private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { - if (action.itemId == R.id.action_invite_users_to_room_invite) { - viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees)) + } else { + Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() } } 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 new file mode 100644 index 0000000000..3f3706699f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt @@ -0,0 +1,67 @@ +/* + * 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.matrixto + +import android.os.Bundle +import android.view.View +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.setTextOrHide +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.features.home.AvatarRenderer +import kotlinx.android.synthetic.main.fragment_matrix_to_card.* +import org.matrix.android.sdk.api.util.MatrixItem +import javax.inject.Inject + +class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottomSheetDialogFragment() { + + @Inject lateinit var avatarRenderer: AvatarRenderer + + interface InteractionListener { + fun didTapStartMessage(matrixItem: MatrixItem) + } + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + private var interactionListener: InteractionListener? = null + + override fun getLayoutResId() = R.layout.fragment_matrix_to_card + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + matrixToCardSendMessageButton.debouncedClicks { + interactionListener?.didTapStartMessage(matrixItem) + dismiss() + } + + matrixToCardNameText.setTextOrHide(matrixItem.displayName) + matrixToCardUserIdText.setTextOrHide(matrixItem.id) + avatarRenderer.render(matrixItem, matrixToCardAvatar) + } + + companion object { + const val ARGS = "MatrixToFragment.Args" + + fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet { + return MatrixToBottomSheet(matrixItem).apply { + interactionListener = listener + } + } + } +} 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 9ff103113f..2d0ca86d52 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 @@ -203,8 +203,8 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } - override fun openCreateDirectRoom(context: Context, byQrCode: Boolean) { - val intent = CreateDirectRoomActivity.getIntent(context, byQrCode) + override fun openCreateDirectRoom(context: Context) { + val intent = CreateDirectRoomActivity.getIntent(context) 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 23d24b709c..504fccb63a 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 @@ -56,7 +56,7 @@ interface Navigator { fun openCreateRoom(context: Context, initialName: String = "") - fun openCreateDirectRoom(context: Context, byQrCode: Boolean = false) + fun openCreateDirectRoom(context: Context) fun openInviteUsersToRoom(context: Context, roomId: String) diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt index 2e91091443..e29c197ab8 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt @@ -79,6 +79,17 @@ class RoomMemberProfileController @Inject constructor( divider = false, action = { callback?.onIgnoreClicked() } ) + if (!state.isMine) { + buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) + + buildProfileAction( + id = "direct", + editable = false, + title = stringProvider.getString(R.string.room_member_open_or_create_dm), + dividerColor = dividerColor, + action = { callback?.onOpenDmClicked() } + ) + } } private fun buildRoomMemberActions(state: RoomMemberProfileViewState) { diff --git a/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt b/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt new file mode 100644 index 0000000000..178a283d2c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt @@ -0,0 +1,85 @@ +/* + * 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.usercode + +import android.graphics.Bitmap +import com.google.zxing.BarcodeFormat +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.LuminanceSource +import com.google.zxing.MultiFormatReader +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.ReaderException +import com.google.zxing.Result +import com.google.zxing.common.HybridBinarizer + +// Some helper code from BinaryEye +object QRCodeBitmapDecodeHelper { + + private val multiFormatReader = MultiFormatReader() + private val decoderHints = mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE)) + + fun decodeQRFromBitmap(bitmap: Bitmap): Result? = + decode(bitmap, false) ?: decode(bitmap, true) + + private fun decode(bitmap: Bitmap, invert: Boolean = false): Result? { + val pixels = IntArray(bitmap.width * bitmap.height) + return decode(pixels, bitmap, invert) + } + + private fun decode( + pixels: IntArray, + bitmap: Bitmap, + invert: Boolean = false + ): Result? { + val width = bitmap.width + val height = bitmap.height + if (bitmap.config != Bitmap.Config.ARGB_8888) { + bitmap.copy(Bitmap.Config.ARGB_8888, true) + } else { + bitmap + }.getPixels(pixels, 0, width, 0, 0, width, height) + return decodeLuminanceSource( + RGBLuminanceSource(width, height, pixels), + invert + ) + } + + private fun decodeLuminanceSource( + source: LuminanceSource, + invert: Boolean + ): Result? { + return decodeLuminanceSource( + if (invert) { + source.invert() + } else { + source + } + ) + } + + private fun decodeLuminanceSource(source: LuminanceSource): Result? { + val bitmap = BinaryBitmap(HybridBinarizer(source)) + return try { + multiFormatReader.decode(bitmap, decoderHints) + } catch (e: ReaderException) { + null + } finally { + multiFormatReader.reset() + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt new file mode 100644 index 0000000000..8b4820b06d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.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.usercode + +import android.app.Activity +import android.os.Bundle +import android.view.View +import android.widget.Toast +import com.airbnb.mvrx.activityViewModel +import com.google.zxing.Result +import com.google.zxing.ResultMetadataType +import im.vector.app.R +import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.checkPermissions +import im.vector.app.core.utils.registerForPermissionsResult +import im.vector.lib.multipicker.MultiPicker +import im.vector.lib.multipicker.utils.ImageUtils +import kotlinx.android.synthetic.main.fragment_qr_code_scanner_with_button.* +import me.dm7.barcodescanner.zxing.ZXingScannerView +import org.matrix.android.sdk.api.extensions.tryOrNull +import javax.inject.Inject + +class ScanUserCodeFragment @Inject constructor() + : VectorBaseFragment(), + ZXingScannerView.ResultHandler { + + override fun getLayoutResId() = R.layout.fragment_qr_code_scanner_with_button + + val sharedViewModel: UserCodeSharedViewModel by activityViewModel() + + var autoFocus = true + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + userCodeMyCodeButton.debouncedClicks { + sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) + } + + userCodeOpenGalleryButton.debouncedClicks { + MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher) + } + } + + private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + startCamera() + } + } + + private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult -> + if (activityResult.resultCode == Activity.RESULT_OK) { + MultiPicker + .get(MultiPicker.IMAGE) + .getSelectedFiles(requireActivity(), activityResult.data) + .firstOrNull() + ?.contentUri + ?.let { uri -> + // try to see if it is a valid matrix code + val bitmap = ImageUtils.getBitmap(requireContext(), uri) + ?: return@let Unit.also { + Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show() + } + handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) }) + } + } + } + + private fun startCamera() { + userCodeScannerView.startCamera() + userCodeScannerView.setAutoFocus(autoFocus) + userCodeScannerView.debouncedClicks { + this.autoFocus = !autoFocus + userCodeScannerView.setAutoFocus(autoFocus) + } + } + + override fun onResume() { + super.onResume() + // Register ourselves as a handler for scan results. + userCodeScannerView.setResultHandler(this) + // Start camera on resume + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + startCamera() + } + } + + override fun onPause() { + super.onPause() + // Stop camera on pause + userCodeScannerView.stopCamera() + } + + override fun handleResult(result: Result?) { + if (result === null) { + Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show() + requireActivity().finish() + } else { + val rawBytes = getRawBytes(result) + val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1) + val value = rawBytesStr ?: result.text + sharedViewModel.handle(UserCodeActions.DecodedQRCode(value)) + } + } + + // Copied from https://github.com/markusfisch/BinaryEye/blob/ + // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434 + private fun getRawBytes(result: Result): ByteArray? { + val metadata = result.resultMetadata ?: return null + val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null + var bytes = ByteArray(0) + @Suppress("UNCHECKED_CAST") + for (seg in segments as Iterable) { + bytes += seg + } + // byte segments can never be shorter than the text. + // Zxing cuts off content prefixes like "WIFI:" + return if (bytes.size >= result.text.length) bytes else null + } +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt new file mode 100644 index 0000000000..ab88f79bef --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt @@ -0,0 +1,59 @@ +/* + * 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.usercode + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.activityViewModel +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.features.home.AvatarRenderer +import kotlinx.android.synthetic.main.fragment_user_code_show.* +import javax.inject.Inject + +class ShowUserCodeFragment @Inject constructor( + private val avatarRenderer: AvatarRenderer +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_user_code_show + + val sharedViewModel: UserCodeSharedViewModel by activityViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + showUserCodeClose.debouncedClicks { + sharedViewModel.handle(UserCodeActions.DismissAction) + } + showUserCodeScanButton.debouncedClicks { + doOpenQRCodeScanner() + } + } + + private fun doOpenQRCodeScanner() { + sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SCAN)) + } + + override fun invalidate() = withState(sharedViewModel) { state -> + state.matrixItem?.let { avatarRenderer.render(it, showUserCodeAvatar) } + state.shareLink?.let { showUserCodeQRImage.setData(it) } + showUserCodeCardNameText.setTextOrHide(state.matrixItem?.displayName) + showUserCodeCardUserIdText.setTextOrHide(state.matrixItem?.id) + Unit + } +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt new file mode 100644 index 0000000000..0611e0f8c3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.usercode + +import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.util.MatrixItem + +sealed class UserCodeActions : VectorViewModelAction { + object DismissAction : UserCodeActions() + data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions() + data class DecodedQRCode(val code: String) : UserCodeActions() + data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions() +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt new file mode 100644 index 0000000000..388dc220a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -0,0 +1,126 @@ +/* + * 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.usercode + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.widget.Toast +import androidx.core.app.ActivityCompat +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import com.airbnb.mvrx.MvRx +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.exhaustive +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.features.matrixto.MatrixToBottomSheet +import kotlinx.android.parcel.Parcelize +import kotlinx.android.synthetic.main.activity_simple.* +import org.matrix.android.sdk.api.util.MatrixItem +import javax.inject.Inject +import kotlin.reflect.KClass + +class UserCodeActivity + : VectorBaseActivity(), UserCodeSharedViewModel.Factory, MatrixToBottomSheet.InteractionListener { + + @Inject lateinit var viewModelFactory: UserCodeSharedViewModel.Factory + + val sharedViewModel: UserCodeSharedViewModel by viewModel() + + @Parcelize + data class Args( + val userId: String + ) : Parcelable + + override fun getLayoutRes() = R.layout.activity_simple + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (isFirstCreation()) { + // should be there early for shared element transition + showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) + } + + sharedViewModel.selectSubscribe(this, UserCodeState::mode) { mode -> + when (mode) { + UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) + UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) + is UserCodeState.Mode.RESULT -> { + showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) + MatrixToBottomSheet.create(mode.matrixItem, this).show(supportFragmentManager, "MatrixToBottomSheet") + } + } + } + + sharedViewModel.observeViewEvents { + when (it) { + is UserCodeShareViewEvents.InviteFriend -> TODO() + UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) + UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true + UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false + is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() + is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId) + }.exhaustive + } + } + + private fun showFragment(fragmentClass: KClass, bundle: Bundle) { + if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { + supportFragmentManager.beginTransaction().let { + it.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) + it.replace(R.id.simpleFragmentContainer, + fragmentClass.java, + bundle, + fragmentClass.simpleName + ) + it.commit() + } + } + } + + override fun didTapStartMessage(matrixItem: MatrixItem) { + sharedViewModel.handle(UserCodeActions.StartChattingWithUser(matrixItem)) + } + + override fun onBackPressed() = withState(sharedViewModel) { + when (it.mode) { + UserCodeState.Mode.SHOW -> super.onBackPressed() + is UserCodeState.Mode.RESULT, + UserCodeState.Mode.SCAN -> sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) + } + } + + override fun create(initialState: UserCodeState, args: Args) = + viewModelFactory.create(initialState, args) + + companion object { + fun newIntent(context: Context, userId: String): Intent { + return Intent(context, UserCodeActivity::class.java).apply { + putExtra(MvRx.KEY_ARG, Args(userId)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt new file mode 100644 index 0000000000..26fcffadd2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt @@ -0,0 +1,28 @@ +/* + * 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.usercode + +import im.vector.app.core.platform.VectorViewEvents + +sealed class UserCodeShareViewEvents : VectorViewEvents { + data class InviteFriend(val permalink: String) : UserCodeShareViewEvents() + object Dismiss : UserCodeShareViewEvents() + object ShowWaitingScreen : UserCodeShareViewEvents() + object HideWaitingScreen : UserCodeShareViewEvents() + data class ToastMessage(val message: String) : UserCodeShareViewEvents() + data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt new file mode 100644 index 0000000000..17dd97cffa --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -0,0 +1,161 @@ +/* + * 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.usercode + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.R +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.StringProvider +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.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.create.CreateRoomParams +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.internal.util.awaitCallback + +class UserCodeSharedViewModel @AssistedInject constructor( + @Assisted val initialState: UserCodeState, + @Assisted val args: UserCodeActivity.Args, + private val session: Session, + private val stringProvider: StringProvider, + private val rawService: RawService) : VectorViewModel(initialState) { + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: UserCodeState): UserCodeSharedViewModel? { + val args = viewModelContext.args() + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface") + } + + override fun initialState(viewModelContext: ViewModelContext): UserCodeState? { + return UserCodeState(viewModelContext.args().userId) + } + } + + init { + val user = session.getUser(args.userId) + setState { + copy( + matrixItem = user?.toMatrixItem(), + shareLink = session.permalinkService().createPermalink(args.userId) + ) + } + } + + private fun handleInviteFriend() { + session.permalinkService().createPermalink(initialState.userId)?.let { permalink -> + _viewEvents.post(UserCodeShareViewEvents.InviteFriend(permalink)) + } + } + + @AssistedInject.Factory + interface Factory { + fun create(initialState: UserCodeState, args: UserCodeActivity.Args): UserCodeSharedViewModel + } + + override fun handle(action: UserCodeActions) { + when (action) { + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + } + } + + private fun handleStartChatting(withUser: UserCodeActions.StartChattingWithUser) { + val mxId = withUser.matrixItem.id + val existing = session.getExistingDirectRoomWithUser(mxId) + setState { + copy(mode = UserCodeState.Mode.SHOW) + } + if (existing != null) { + // navigate to this room + _viewEvents.post(UserCodeShareViewEvents.NavigateToRoom(existing)) + } else { + // we should create the room then navigate + _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen) + viewModelScope.launch(Dispatchers.IO) { + val adminE2EByDefault = rawService.getElementWellknown(session.myUserId) + ?.isE2EByDefault() + ?: true + + val roomParams = CreateRoomParams() + .apply { + invitedUserIds.add(mxId) + setDirectMessage() + enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault + } + + val roomId = + try { + awaitCallback { session.createRoom(roomParams, it) } + } catch (failure: Throwable) { + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.invite_users_to_room_failure))) + return@launch + } finally { + _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) + } + _viewEvents.post(UserCodeShareViewEvents.NavigateToRoom(roomId)) + } + } + } + + private fun handleQrCodeDecoded(action: UserCodeActions.DecodedQRCode) { + val linkedId = PermalinkParser.parse(action.code) + if (linkedId is PermalinkData.FallbackLink) { + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_a_valid_qr_code))) + return + } + _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen) + viewModelScope.launch(Dispatchers.IO) { + when (linkedId) { + is PermalinkData.RoomLink -> TODO() + is PermalinkData.UserLink -> { + var user = session.getUser(linkedId.userId) ?: awaitCallback> { + session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it) + }.firstOrNull { it.userId == linkedId.userId } + // Create raw Uxid in case the user is not searchable + ?: User(linkedId.userId, null, null) + + setState { + copy( + mode = UserCodeState.Mode.RESULT(user.toMatrixItem()) + ) + } + } + is PermalinkData.GroupLink -> TODO() + is PermalinkData.FallbackLink -> TODO() + } + _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt new file mode 100644 index 0000000000..3be882af3d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.usercode + +import com.airbnb.mvrx.MvRxState +import org.matrix.android.sdk.api.util.MatrixItem + +data class UserCodeState( + val userId: String, + val matrixItem: MatrixItem? = null, + val shareLink: String? = null, + val mode: Mode = Mode.SHOW +) : MvRxState { + sealed class Mode { + object SHOW : Mode() + object SCAN : Mode() + data class RESULT(val matrixItem: MatrixItem) : Mode() + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt new file mode 100644 index 0000000000..2307640634 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt @@ -0,0 +1,53 @@ +/* + * 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.userdirectory + +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +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_contact_action) +abstract class ActionItem : VectorEpoxyModel() { + + @EpoxyAttribute var title: CharSequence? = null + @EpoxyAttribute @DrawableRes var actionIconRes: Int? = null + @EpoxyAttribute var clickAction: View.OnClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener(clickAction) + // If name is empty, use userId as name and force it being centered + holder.actionTitleText.setTextOrHide(title) + if (actionIconRes != null) { + holder.actionTitleImageView.setImageResource(actionIconRes!!) + } else { + holder.actionTitleImageView.setImageDrawable(null) + } + } + + class Holder : VectorEpoxyHolder() { + val actionTitleText by bind(R.id.actionTitleText) + val actionTitleImageView by bind(R.id.actionIconImageView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt new file mode 100644 index 0000000000..ee96c34f45 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt @@ -0,0 +1,47 @@ +/* + * 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.userdirectory + +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.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.setTextOrHide + +@EpoxyModelClass(layout = R.layout.item_contact_detail) +abstract class ContactDetailItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var threePid: String + @EpoxyAttribute var matrixId: String? = null + @EpoxyAttribute var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.onClick(clickListener) + holder.nameView.text = threePid + holder.matrixIdView.setTextOrHide(matrixId) + } + + class Holder : VectorEpoxyHolder() { + val nameView by bind(R.id.contactDetailName) + val matrixIdView by bind(R.id.contactDetailMatrixId) + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt new file mode 100644 index 0000000000..d9f424d961 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.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.userdirectory + +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.contacts.MappedContact +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.features.home.AvatarRenderer + +@EpoxyModelClass(layout = R.layout.item_contact_main) +abstract class ContactItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var mappedContact: MappedContact + + override fun bind(holder: Holder) { + super.bind(holder) + // If name is empty, use userId as name and force it being centered + holder.nameView.text = mappedContact.displayName + avatarRenderer.render(mappedContact, holder.avatarImageView) + } + + class Holder : VectorEpoxyHolder() { + val nameView by bind(R.id.contactDisplayName) + val avatarImageView by bind(R.id.contactAvatar) + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt deleted file mode 100644 index e68d9855dd..0000000000 --- a/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.userdirectory - -import com.airbnb.epoxy.EpoxyController -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Loading -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.Uninitialized -import im.vector.app.R -import im.vector.app.core.epoxy.errorWithRetryItem -import im.vector.app.core.epoxy.loadingItem -import im.vector.app.core.epoxy.noResultItem -import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.resources.StringProvider -import im.vector.app.features.home.AvatarRenderer -import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.toMatrixItem -import javax.inject.Inject - -class DirectoryUsersController @Inject constructor(private val session: Session, - private val avatarRenderer: AvatarRenderer, - private val stringProvider: StringProvider, - private val errorFormatter: ErrorFormatter) : EpoxyController() { - - private var state: UserDirectoryViewState? = null - - var callback: Callback? = null - - init { - requestModelBuild() - } - - fun setData(state: UserDirectoryViewState) { - this.state = state - requestModelBuild() - } - - override fun buildModels() { - val currentState = state ?: return - val hasSearch = currentState.directorySearchTerm.isNotBlank() - when (val asyncUsers = currentState.directoryUsers) { - is Uninitialized -> renderEmptyState(false) - is Loading -> renderLoading() - is Success -> renderSuccess( - computeUsersList(asyncUsers(), currentState.directorySearchTerm), - currentState.getSelectedMatrixId(), - hasSearch - ) - is Fail -> renderFailure(asyncUsers.error) - } - } - - /** - * Eventually add the searched terms, if it is a userId, and if not already present in the result - */ - private fun computeUsersList(directoryUsers: List, searchTerms: String): List { - return directoryUsers + - searchTerms - .takeIf { terms -> MatrixPatterns.isUserId(terms) && !directoryUsers.any { it.userId == terms } } - ?.let { listOf(User(it)) } - .orEmpty() - } - - private fun renderLoading() { - loadingItem { - id("loading") - } - } - - private fun renderFailure(failure: Throwable) { - errorWithRetryItem { - id("error") - text(errorFormatter.toHumanReadable(failure)) - listener { callback?.retryDirectoryUsersRequest() } - } - } - - private fun renderSuccess(users: List, - selectedUsers: List, - hasSearch: Boolean) { - if (users.isEmpty()) { - renderEmptyState(hasSearch) - } else { - renderUsers(users, selectedUsers) - } - } - - private fun renderUsers(users: List, selectedUsers: List) { - for (user in users) { - if (user.userId == session.myUserId) { - continue - } - val isSelected = selectedUsers.contains(user.userId) - userDirectoryUserItem { - id(user.userId) - selected(isSelected) - matrixItem(user.toMatrixItem()) - avatarRenderer(avatarRenderer) - clickListener { _ -> - callback?.onItemClick(user) - } - } - } - } - - private fun renderEmptyState(hasSearch: Boolean) { - val noResultRes = if (hasSearch) { - R.string.no_result_placeholder - } else { - R.string.direct_room_start_search - } - noResultItem { - id("noResult") - text(stringProvider.getString(noResultRes)) - } - } - - interface Callback { - fun onItemClick(user: User) - fun retryDirectoryUsersRequest() - } -} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt deleted file mode 100644 index 4fbb9bbb41..0000000000 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.userdirectory - -import com.airbnb.epoxy.EpoxyModel -import com.airbnb.epoxy.paging.PagedListEpoxyController -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.Uninitialized -import im.vector.app.R -import im.vector.app.core.epoxy.EmptyItem_ -import im.vector.app.core.epoxy.loadingItem -import im.vector.app.core.epoxy.noResultItem -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.createUIHandler -import im.vector.app.features.home.AvatarRenderer -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.toMatrixItem -import javax.inject.Inject - -class KnownUsersController @Inject constructor(private val session: Session, - private val avatarRenderer: AvatarRenderer, - private val stringProvider: StringProvider) : PagedListEpoxyController( - modelBuildingHandler = createUIHandler() -) { - - private var selectedUsers: List = emptyList() - private var users: Async> = Uninitialized - private var isFiltering: Boolean = false - - var callback: Callback? = null - - init { - requestModelBuild() - } - - fun setData(state: UserDirectoryViewState) { - this.isFiltering = !state.filterKnownUsersValue.isEmpty() - val newSelection = state.getSelectedMatrixId() - this.users = state.knownUsers - if (newSelection != selectedUsers) { - this.selectedUsers = newSelection - requestForcedModelBuild() - } - submitList(state.knownUsers()) - } - - override fun buildItemModel(currentPosition: Int, item: User?): EpoxyModel<*> { - return if (item == null) { - EmptyItem_().id(currentPosition) - } else { - val isSelected = selectedUsers.contains(item.userId) - UserDirectoryUserItem_() - .id(item.userId) - .selected(isSelected) - .matrixItem(item.toMatrixItem()) - .avatarRenderer(avatarRenderer) - .clickListener { _ -> - callback?.onItemClick(item) - } - } - } - - override fun addModels(models: List>) { - if (users is Incomplete) { - renderLoading() - } else if (models.isEmpty()) { - renderEmptyState() - } else { - var lastFirstLetter: String? = null - for (model in models) { - if (model is UserDirectoryUserItem) { - if (model.matrixItem.id == session.myUserId) continue - val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName() - val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter - lastFirstLetter = currentFirstLetter - - UserDirectoryLetterHeaderItem_() - .id(currentFirstLetter) - .letter(currentFirstLetter) - .addIf(showLetter, this) - - model.addTo(this) - } else { - continue - } - } - } - } - - private fun renderLoading() { - loadingItem { - id("loading") - } - } - - private fun renderEmptyState() { - noResultItem { - id("noResult") - text(stringProvider.getString(R.string.direct_room_no_known_users)) - } - } - - interface Callback { - fun onItemClick(user: User) - } -} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt deleted file mode 100644 index 70ea9141e7..0000000000 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.userdirectory - -import android.os.Bundle -import android.view.View -import com.airbnb.mvrx.activityViewModel -import com.airbnb.mvrx.withState -import com.jakewharton.rxbinding3.widget.textChanges -import im.vector.app.R -import im.vector.app.core.extensions.cleanup -import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.hideKeyboard -import im.vector.app.core.extensions.setupAsSearch -import im.vector.app.core.extensions.showKeyboard -import im.vector.app.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.fragment_user_directory.* -import org.matrix.android.sdk.api.session.user.model.User -import javax.inject.Inject - -class UserDirectoryFragment @Inject constructor( - private val directRoomController: DirectoryUsersController -) : VectorBaseFragment(), DirectoryUsersController.Callback { - - override fun getLayoutResId() = R.layout.fragment_user_directory - private val viewModel: UserDirectoryViewModel by activityViewModel() - - private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) - setupRecyclerView() - setupSearchByMatrixIdView() - setupCloseView() - } - - override fun onDestroyView() { - userDirectoryRecyclerView.cleanup() - directRoomController.callback = null - super.onDestroyView() - } - - private fun setupRecyclerView() { - directRoomController.callback = this - userDirectoryRecyclerView.configureWith(directRoomController) - } - - private fun setupSearchByMatrixIdView() { - userDirectorySearchById.setupAsSearch(searchIconRes = 0) - userDirectorySearchById - .textChanges() - .subscribe { - viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(it.toString())) - } - .disposeOnDestroyView() - userDirectorySearchById.showKeyboard(andRequestFocus = true) - } - - private fun setupCloseView() { - userDirectoryClose.debouncedClicks { - sharedActionViewModel.post(UserDirectorySharedAction.GoBack) - } - } - - override fun invalidate() = withState(viewModel) { - directRoomController.setData(it) - } - - override fun onItemClick(user: User) { - view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) - sharedActionViewModel.post(UserDirectorySharedAction.GoBack) - } - - override fun retryDirectoryUsersRequest() { - val currentSearch = userDirectorySearchById.text.toString() - viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(currentSearch)) - } -} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt deleted file mode 100644 index 0a24b85ce2..0000000000 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.userdirectory - -import androidx.fragment.app.FragmentActivity -import arrow.core.Option -import com.airbnb.mvrx.ActivityViewModelContext -import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.ViewModelContext -import com.jakewharton.rxrelay2.BehaviorRelay -import com.squareup.inject.assisted.Assisted -import com.squareup.inject.assisted.AssistedInject -import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.extensions.toggle -import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.createdirect.CreateDirectRoomActivity -import im.vector.app.features.invite.InviteUsersToRoomActivity -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit - -private typealias KnowUsersFilter = String -private typealias DirectoryUsersSearch = String - -class UserDirectoryViewModel @AssistedInject constructor(@Assisted - initialState: UserDirectoryViewState, - private val session: Session) - : VectorViewModel(initialState) { - - @AssistedInject.Factory - interface Factory { - fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel - } - - private val knownUsersFilter = BehaviorRelay.createDefault>(Option.empty()) - private val directoryUsersSearch = BehaviorRelay.create() - - companion object : MvRxViewModelFactory { - - override fun create(viewModelContext: ViewModelContext, state: UserDirectoryViewState): UserDirectoryViewModel? { - return when (viewModelContext) { - is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state) - is ActivityViewModelContext -> { - when (viewModelContext.activity()) { - is CreateDirectRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) - is InviteUsersToRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) - else -> error("Wrong activity or fragment") - } - } - else -> error("Wrong activity or fragment") - } - } - } - - init { - observeKnownUsers() - observeDirectoryUsers() - } - - override fun handle(action: UserDirectoryAction) { - when (action) { - is UserDirectoryAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value)) - is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty()) - is UserDirectoryAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value) - is UserDirectoryAction.SelectPendingInvitee -> handleSelectUser(action) - is UserDirectoryAction.RemovePendingInvitee -> handleRemoveSelectedUser(action) - }.exhaustive - } - - private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemovePendingInvitee) = withState { state -> - val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee) - setState { - copy( - pendingInvitees = selectedUsers, - existingDmRoomId = getExistingDmRoomId(selectedUsers) - ) - } - } - - private fun handleSelectUser(action: UserDirectoryAction.SelectPendingInvitee) = withState { state -> - // Reset the filter asap - directoryUsersSearch.accept("") - val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee) - setState { - copy( - pendingInvitees = selectedUsers, - existingDmRoomId = getExistingDmRoomId(selectedUsers) - ) - } - } - - private fun getExistingDmRoomId(selectedUsers: Set): String? { - return selectedUsers - .takeIf { it.size == 1 } - ?.filterIsInstance(PendingInvitee.UserPendingInvitee::class.java) - ?.firstOrNull() - ?.let { invitee -> session.getExistingDirectRoomWithUser(invitee.user.userId) } - } - - private fun observeDirectoryUsers() = withState { state -> - directoryUsersSearch - .debounce(300, TimeUnit.MILLISECONDS) - .switchMapSingle { search -> - val stream = if (search.isBlank()) { - Single.just(emptyList()) - } else { - session.rx() - .searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet()) - .map { users -> - users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } - } - } - stream.toAsync { - copy(directoryUsers = it, directorySearchTerm = search) - } - } - .subscribe() - .disposeOnClear() - } - - private fun observeKnownUsers() = withState { state -> - knownUsersFilter - .throttleLast(300, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - session.rx().livePagedUsers(it.orNull(), state.excludedUserIds) - } - .execute { async -> - copy( - knownUsers = async, - filterKnownUsersValue = knownUsersFilter.value ?: Option.empty() - ) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt similarity index 71% rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryAction.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt index f4f3fb8cd4..0c2c4b1f4b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt @@ -18,10 +18,10 @@ package im.vector.app.features.userdirectory import im.vector.app.core.platform.VectorViewModelAction -sealed class UserDirectoryAction : VectorViewModelAction { - data class FilterKnownUsers(val value: String) : UserDirectoryAction() - data class SearchDirectoryUsers(val value: String) : UserDirectoryAction() - object ClearFilterKnownUsers : UserDirectoryAction() - data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction() - data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction() +sealed class UserListAction : VectorViewModelAction { + data class SearchUsers(val value: String) : UserListAction() + object ClearSearchUsers : UserListAction() + data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction() + data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction() + object ComputeMatrixToLinkForSharing : UserListAction() } 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 new file mode 100644 index 0000000000..6cd76401fd --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt @@ -0,0 +1,197 @@ +/* + * 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.userdirectory + +import android.view.View +import com.airbnb.epoxy.EpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.app.R +import im.vector.app.core.epoxy.errorWithRetryItem +import im.vector.app.core.epoxy.loadingItem +import im.vector.app.core.epoxy.noResultItem +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class UserListController @Inject constructor(private val session: Session, + private val avatarRenderer: AvatarRenderer, + private val stringProvider: StringProvider, + private val errorFormatter: ErrorFormatter) : EpoxyController() { + + private var state: UserListViewState? = null + + var callback: Callback? = null + + fun setData(state: UserListViewState) { + this.state = state + requestModelBuild() + } + + override fun buildModels() { + val currentState = state ?: return + + // Build generic items + if (currentState.searchTerm.isBlank()) { + // For now we remove this option if in invite to existing room flow (and not create DM) + if (currentState.pendingInvitees.isEmpty() + // For now we remove this option if in invite to existing room flow (and not create DM) + && currentState.existingRoomId == null) { + actionItem { + id(R.drawable.ic_invite_people) + title(stringProvider.getString(R.string.invite_friends)) + actionIconRes(R.drawable.ic_invite_people) + clickAction(View.OnClickListener { + callback?.onInviteFriendClick() + }) + } + } + actionItem { + id(R.drawable.ic_book) + title(stringProvider.getString(R.string.contacts_book_title)) + actionIconRes(R.drawable.ic_book) + clickAction(View.OnClickListener { + callback?.onContactBookClick() + }) + } + if (currentState.pendingInvitees.isEmpty() + // For now we remove this option if in invite to existing room flow (and not create DM) + && currentState.existingRoomId == null) { + actionItem { + id(R.drawable.ic_qr_code_add) + title(stringProvider.getString(R.string.qr_code)) + actionIconRes(R.drawable.ic_qr_code_add) + clickAction(View.OnClickListener { + callback?.onUseQRCode() + }) + } + } + } + + when (currentState.knownUsers) { + is Uninitialized -> renderEmptyState() + is Loading -> renderLoading() + is Fail -> renderFailure(currentState.knownUsers.error) + is Success -> buildKnownUsers(currentState, currentState.getSelectedMatrixId()) + } + + when (val asyncUsers = currentState.directoryUsers) { + is Uninitialized -> { + } + is Loading -> renderLoading() + is Fail -> renderFailure(asyncUsers.error) + is Success -> buildDirectoryUsers( + asyncUsers(), + currentState.getSelectedMatrixId(), + currentState.searchTerm, + // to avoid showing twice same user in known and suggestions + currentState.knownUsers.invoke()?.map { it.userId } ?: emptyList() + ) + } + } + + 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)) + } + + 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) { + val toDisplay = directoryUsers.filter { !ignoreIds.contains(it.userId) } + if (toDisplay.isEmpty() && searchTerms.isBlank()) { + return + } + userListHeaderItem { + id("suggestions") + header(stringProvider.getString(R.string.direct_room_user_list_suggestions_title)) + } + if (toDisplay.isEmpty()) { + 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) + } + } + } + } + } + } + + private fun renderLoading() { + loadingItem { + id("loading") + } + } + + private fun renderEmptyState() { + noResultItem { + id("noResult") + text(stringProvider.getString(R.string.no_result_placeholder)) + } + } + + private fun renderFailure(failure: Throwable) { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(failure)) + } + } + + interface Callback { + fun onInviteFriendClick() + fun onContactBookClick() + fun onUseQRCode() + fun onItemClick(user: User) + fun onMatrixIdClick(matrixId: String) + fun onThreePidClick(threePid: ThreePid) + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt similarity index 57% rename from vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index ec684e8eea..4af16772b8 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -36,53 +36,64 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.setupAsSearch import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.DimensionConverter +import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel -import kotlinx.android.synthetic.main.fragment_known_users.* +import kotlinx.android.synthetic.main.fragment_user_list.* +import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.user.model.User import javax.inject.Inject -class KnownUsersFragment @Inject constructor( - val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory, - private val knownUsersController: KnownUsersController, +class UserListFragment @Inject constructor( + private val userListController: UserListController, private val dimensionConverter: DimensionConverter, val homeServerCapabilitiesViewModelFactory: HomeServerCapabilitiesViewModel.Factory -) : VectorBaseFragment(), KnownUsersController.Callback { +) : VectorBaseFragment(), UserListController.Callback { - private val args: KnownUsersFragmentArgs by args() + private val args: UserListFragmentArgs by args() + private val viewModel: UserListViewModel by activityViewModel() + private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel() + private lateinit var sharedActionViewModel: UserListSharedActionViewModel - override fun getLayoutResId() = R.layout.fragment_known_users + override fun getLayoutResId() = R.layout.fragment_user_list override fun getMenuRes() = args.menuResId - private val viewModel: UserDirectoryViewModel by activityViewModel() - private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel() - - private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java) + userListTitle.text = args.title + vectorBaseActivity.setSupportActionBar(userListToolbar) - knownUsersTitle.text = args.title - vectorBaseActivity.setSupportActionBar(knownUsersToolbar) setupRecyclerView() - setupFilterView() - setupAddByMatrixIdView() - setupAddFromPhoneBookView() + setupSearchView() setupCloseView() homeServerCapabilitiesViewModel.subscribe { - knownUsersE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault + userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault } - viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) { + viewModel.selectSubscribe(this, UserListViewState::pendingInvitees) { renderSelectedUsers(it) } + + viewModel.observeViewEvents { + when (it) { + is UserListViewEvents.OpenShareMatrixToLing -> { + val text = getString(R.string.invite_friends_text, it.link) + startSharePlainTextIntent( + fragment = this, + activityResultLauncher = null, + chooserTitle = getString(R.string.invite_friends), + text = text, + extraTitle = getString(R.string.invite_friends_rich_title) + ) + } + } + } } override fun onDestroyView() { - knownUsersController.callback = null - knownUsersRecyclerView.cleanup() + recyclerView.cleanup() super.onDestroyView() } @@ -91,69 +102,52 @@ class KnownUsersFragment @Inject constructor( val showMenuItem = it.pendingInvitees.isNotEmpty() menu.forEach { menuItem -> menuItem.isVisible = showMenuItem - if (args.isCreatingRoom) { - menuItem.setTitle(if (it.existingDmRoomId != null) R.string.action_open else R.string.create_room_action_create) - } } } super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { - sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected( - item.itemId, - it.pendingInvitees, - it.existingDmRoomId - )) + sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees)) return@withState true } - private fun setupAddByMatrixIdView() { - addByMatrixId.debouncedClicks { - sharedActionViewModel.post(UserDirectorySharedAction.OpenUsersDirectory) - } - } - - private fun setupAddFromPhoneBookView() { - addFromPhoneBook.debouncedClicks { - // TODO handle Permission first - sharedActionViewModel.post(UserDirectorySharedAction.OpenPhoneBook) - } - } - private fun setupRecyclerView() { - knownUsersController.callback = this + userListController.callback = this // Don't activate animation as we might have way to much item animation when filtering - knownUsersRecyclerView.configureWith(knownUsersController, disableItemAnimation = true) + recyclerView.configureWith(userListController, disableItemAnimation = true) } - private fun setupFilterView() { - knownUsersFilter + private fun setupSearchView() { + withState(viewModel) { + userListSearch.hint = getString(R.string.user_directory_search_hint, it.myUserId) + } + userListSearch .textChanges() - .startWith(knownUsersFilter.text) + .startWith(userListSearch.text) .subscribe { text -> - val filterValue = text.trim() - val action = if (filterValue.isBlank()) { - UserDirectoryAction.ClearFilterKnownUsers + val searchValue = text.trim() + val action = if (searchValue.isBlank()) { + UserListAction.ClearSearchUsers } else { - UserDirectoryAction.FilterKnownUsers(filterValue.toString()) + UserListAction.SearchUsers(searchValue.toString()) } viewModel.handle(action) } .disposeOnDestroyView() - knownUsersFilter.setupAsSearch() - knownUsersFilter.requestFocus() + userListSearch.setupAsSearch() + userListSearch.requestFocus() } private fun setupCloseView() { - knownUsersClose.debouncedClicks { + userListClose.debouncedClicks { requireActivity().finish() } } override fun invalidate() = withState(viewModel) { - knownUsersController.setData(it) + userListController.setData(it) } private fun renderSelectedUsers(invitees: Set) { @@ -183,12 +177,35 @@ class KnownUsersFragment @Inject constructor( chip.isCloseIconVisible = true chipGroup.addView(chip) chip.setOnCloseIconClickListener { - viewModel.handle(UserDirectoryAction.RemovePendingInvitee(pendingInvitee)) + viewModel.handle(UserListAction.RemovePendingInvitee(pendingInvitee)) } } + override fun onInviteFriendClick() { + viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing) + } + + override fun onContactBookClick() { + sharedActionViewModel.post(UserListSharedAction.OpenPhoneBook) + } + override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) + viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) + } + + override fun onMatrixIdClick(matrixId: String) { + view?.hideKeyboard() + viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) + } + + override fun onThreePidClick(threePid: ThreePid) { + view?.hideKeyboard() + viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) + } + + override fun onUseQRCode() { + view?.hideKeyboard() + sharedActionViewModel.post(UserListSharedAction.AddByQrCode) } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt similarity index 91% rename from vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt index c20aedb803..041f29a77a 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt @@ -20,9 +20,9 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -data class KnownUsersFragmentArgs( +data class UserListFragmentArgs( val title: String, val menuResId: Int, val excludedUserIds: Set? = null, - val isCreatingRoom: Boolean = false + val existingRoomId: String? = null ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt new file mode 100644 index 0000000000..82fa4a4d6f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt @@ -0,0 +1,39 @@ +/* + * 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.userdirectory + +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 + +@EpoxyModelClass(layout = R.layout.item_user_list_header) +abstract class UserListHeaderItem : VectorEpoxyModel() { + + @EpoxyAttribute var header: String = "" + + override fun bind(holder: Holder) { + super.bind(holder) + holder.headerTextView.text = header + } + + class Holder : VectorEpoxyHolder() { + val headerTextView by bind(R.id.userListHeaderView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt similarity index 59% rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt index 14daa67f25..b2cdee3e63 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt @@ -18,12 +18,10 @@ package im.vector.app.features.userdirectory import im.vector.app.core.platform.VectorSharedAction -sealed class UserDirectorySharedAction : VectorSharedAction { - object OpenUsersDirectory : UserDirectorySharedAction() - object OpenPhoneBook : UserDirectorySharedAction() - object Close : UserDirectorySharedAction() - object GoBack : UserDirectorySharedAction() - data class OnMenuItemSelected(val itemId: Int, - val invitees: Set, - val existingDmRoomId: String?) : UserDirectorySharedAction() +sealed class UserListSharedAction : VectorSharedAction { + object Close : UserListSharedAction() + object GoBack : UserListSharedAction() + data class OnMenuItemSelected(val itemId: Int, val invitees: Set) : UserListSharedAction() + object OpenPhoneBook : UserListSharedAction() + object AddByQrCode : UserListSharedAction() } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedActionViewModel.kt similarity index 85% rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedActionViewModel.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedActionViewModel.kt index b63682e57a..05ebc73cff 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedActionViewModel.kt @@ -19,4 +19,4 @@ package im.vector.app.features.userdirectory import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject -class UserDirectorySharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() +class UserListSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewEvents.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt similarity index 85% rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewEvents.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt index bfbdc657ef..95c6729fad 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt @@ -21,4 +21,6 @@ import im.vector.app.core.platform.VectorViewEvents /** * Transient events for invite users to room screen */ -sealed class UserDirectoryViewEvents : VectorViewEvents +sealed class UserListViewEvents : VectorViewEvents { + data class OpenShareMatrixToLing(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 new file mode 100644 index 0000000000..1011a3e28a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt @@ -0,0 +1,204 @@ +/* + * 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.userdirectory + +import com.airbnb.mvrx.ActivityViewModelContext +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.jakewharton.rxrelay2.BehaviorRelay +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.extensions.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 +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.rx.rx +import java.util.concurrent.TimeUnit + +private typealias KnownUsersSearch = String +private typealias DirectoryUsersSearch = String + +class UserListViewModel @AssistedInject constructor(@Assisted initialState: UserListViewState, + @Assisted args: UserListFragmentArgs, + private val session: Session) + : VectorViewModel(initialState) { + + private val knownUsersSearch = BehaviorRelay.create() + private val directoryUsersSearch = BehaviorRelay.create() + + private var currentUserSearchDisposable: Disposable? = null + + @AssistedInject.Factory + interface Factory { + fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel + } + + companion object : MvRxViewModelFactory { + + private val USER_NOT_FOUND_MAP = emptyMap() + private val USER_NOT_FOUND = User("") + + override fun create(viewModelContext: ViewModelContext, state: UserListViewState): UserListViewModel? { + val factory = when (viewModelContext) { + is FragmentViewModelContext -> viewModelContext.fragment as? Factory + is ActivityViewModelContext -> viewModelContext.activity as? Factory + } + val args = viewModelContext.args() + return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface") + } + } + + init { + setState { + copy( + myUserId = session.myUserId, + existingRoomId = args.existingRoomId + ) + } + observeUsers() + } + + override fun handle(action: UserListAction) { + when (action) { + is UserListAction.SearchUsers -> handleSearchUsers(action.value) + is UserListAction.ClearSearchUsers -> handleClearSearchUsers() + is UserListAction.SelectPendingInvitee -> handleSelectUser(action) + is UserListAction.RemovePendingInvitee -> handleRemoveSelectedUser(action) + UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink() + }.exhaustive + } + + private fun handleSearchUsers(searchTerm: String) { + setState { + copy(searchTerm = searchTerm) + } + knownUsersSearch.accept(searchTerm) + directoryUsersSearch.accept(searchTerm) + } + + private fun handleShareMyMatrixToLink() { + session.permalinkService().createPermalink(session.myUserId)?.let { + _viewEvents.post(UserListViewEvents.OpenShareMatrixToLing(it)) + } + } + + private fun handleClearSearchUsers() { + knownUsersSearch.accept("") + directoryUsersSearch.accept("") + setState { + copy(searchTerm = "") + } + } + + private fun observeUsers() = withState { state -> + knownUsersSearch + .throttleLast(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { + session.rx().livePagedUsers(it, state.excludedUserIds) + } + .execute { async -> + copy(knownUsers = async) + } + + currentUserSearchDisposable?.dispose() + directoryUsersSearch + .debounce(300, TimeUnit.MILLISECONDS) + .switchMapSingle { search -> + val stream = if (search.isBlank()) { + Single.just(emptyList()) + } else if (MatrixPatterns.isUserId(search)) { + // If it's a valid user id try to use Profile API + // because directory only returns users that are in public rooms or share a room with you, where as + // profile will work other federations + session.rx().searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet()) + .map { users -> + users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } + } + .zipWith( + session.rx().getProfileInfo(search) + // ... not sure how to handle that properly (manage error case in map and return optional) + .onErrorReturn { USER_NOT_FOUND_MAP } + .map { json -> + if (json === USER_NOT_FOUND_MAP) { + USER_NOT_FOUND + } else { + User( + userId = search, + displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String, + avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String + ) + } + }, + { t1, t2 -> + if (t2 == USER_NOT_FOUND) { + t1 + } + // profile result might also be in search results, in this case keep search result + else if (t1.indexOfFirst { it.userId == t2.userId } != -1) { + t1 + } else { + // put it first + listOf(t2) + t1 + } + } + ) + .doOnSubscribe { + currentUserSearchDisposable = it + } + .doOnDispose { + currentUserSearchDisposable = null + } + } else { + session.rx() + .searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet()) + .map { users -> + users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() } + } + .doOnSubscribe { + currentUserSearchDisposable = it + } + .doOnDispose { + currentUserSearchDisposable = null + } + } + stream.toAsync { + copy(directoryUsers = it) + } + } + .subscribe() + .disposeOnClear() + } + + private fun handleSelectUser(action: UserListAction.SelectPendingInvitee) = withState { state -> + val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee) + setState { copy(pendingInvitees = selectedUsers) } + } + + private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingInvitee) = withState { state -> + val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee) + setState { copy(pendingInvitees = selectedUsers) } + } +} diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt similarity index 78% rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index fe79a8ab37..f7cf421ca8 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -17,30 +17,29 @@ package im.vector.app.features.userdirectory import androidx.paging.PagedList -import arrow.core.Option import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.app.core.contacts.MappedContact import org.matrix.android.sdk.api.session.user.model.User -data class UserDirectoryViewState( +data class UserListViewState( val excludedUserIds: Set? = null, val knownUsers: Async> = Uninitialized, val directoryUsers: Async> = Uninitialized, + val filteredMappedContacts: List = emptyList(), val pendingInvitees: Set = emptySet(), val createAndInviteState: Async = Uninitialized, - val directorySearchTerm: String = "", - val filterKnownUsersValue: Option = Option.empty(), - val existingDmRoomId: String? = null + val searchTerm: String = "", + val myUserId: String = "", + val existingRoomId: String? = null ) : MvRxState { - constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds) - fun getSelectedMatrixId(): List { return pendingInvitees .mapNotNull { when (it) { - is PendingInvitee.UserPendingInvitee -> it.user.userId + is PendingInvitee.UserPendingInvitee -> it.user.userId is PendingInvitee.ThreePidPendingInvitee -> null } } diff --git a/vector/src/main/res/drawable/ic_book.xml b/vector/src/main/res/drawable/ic_book.xml new file mode 100644 index 0000000000..3cd7357248 --- /dev/null +++ b/vector/src/main/res/drawable/ic_book.xml @@ -0,0 +1,21 @@ + + + + diff --git a/vector/src/main/res/drawable/ic_invite_people.xml b/vector/src/main/res/drawable/ic_invite_people.xml new file mode 100644 index 0000000000..3ec60095ff --- /dev/null +++ b/vector/src/main/res/drawable/ic_invite_people.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_picture_icon.xml b/vector/src/main/res/drawable/ic_picture_icon.xml new file mode 100644 index 0000000000..c978a714ab --- /dev/null +++ b/vector/src/main/res/drawable/ic_picture_icon.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/vector/src/main/res/drawable/ic_qr_code_add.xml b/vector/src/main/res/drawable/ic_qr_code_add.xml new file mode 100644 index 0000000000..32e41f6e57 --- /dev/null +++ b/vector/src/main/res/drawable/ic_qr_code_add.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/activity_simple.xml b/vector/src/main/res/layout/activity_simple.xml index 0eda46a67d..c6f2af8171 100644 --- a/vector/src/main/res/layout/activity_simple.xml +++ b/vector/src/main/res/layout/activity_simple.xml @@ -1,7 +1,7 @@ - @@ -10,4 +10,24 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_home_drawer.xml b/vector/src/main/res/layout/fragment_home_drawer.xml index d34ea0b815..2f1d7cc4c4 100644 --- a/vector/src/main/res/layout/fragment_home_drawer.xml +++ b/vector/src/main/res/layout/fragment_home_drawer.xml @@ -31,10 +31,11 @@ @@ -43,13 +44,13 @@ android:id="@+id/homeDrawerUsernameView" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="24dp" - android:layout_marginEnd="@dimen/layout_horizontal_margin" + android:layout_marginTop="16dp" + android:layout_marginEnd="8dp" android:maxLines="1" android:singleLine="true" android:textColor="?riotx_text_primary" android:textSize="15sp" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@+id/homeDrawerQRCodeButton" app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView" tools:text="@sample/matrix.json/data/displayName" /> @@ -58,18 +59,69 @@ android:id="@+id/homeDrawerUserIdView" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/layout_horizontal_margin" - android:layout_marginBottom="17dp" + android:layout_marginEnd="8dp" android:maxLines="1" android:singleLine="true" android:textColor="?riotx_text_secondary" android:textSize="15sp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toTopOf="@+id/homeDrawerInviteFriendButton" + app:layout_constraintEnd_toStartOf="@+id/homeDrawerQRCodeButton" app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView" app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView" tools:text="@sample/matrix.json/data/mxid" /> + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_qr_code_scanner.xml b/vector/src/main/res/layout/fragment_qr_code_scanner.xml index 589b7c73d4..135a856f4a 100644 --- a/vector/src/main/res/layout/fragment_qr_code_scanner.xml +++ b/vector/src/main/res/layout/fragment_qr_code_scanner.xml @@ -15,4 +15,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml b/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml new file mode 100644 index 0000000000..6a59138990 --- /dev/null +++ b/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml @@ -0,0 +1,51 @@ + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index 855c45f7c5..72266cc21a 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -23,16 +23,20 @@ tools:showPaths="true" tools:visibility="visible" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_user_list.xml similarity index 68% rename from vector/src/main/res/layout/fragment_known_users.xml rename to vector/src/main/res/layout/fragment_user_list.xml index cf2d4e8025..15884502ad 100644 --- a/vector/src/main/res/layout/fragment_known_users.xml +++ b/vector/src/main/res/layout/fragment_user_list.xml @@ -1,5 +1,5 @@ - @@ -67,7 +67,7 @@ android:layout_marginEnd="@dimen/layout_horizontal_margin" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/knownUsersToolbar" + app:layout_constraintTop_toBottomOf="@+id/userListToolbar" app:maxHeight="64dp"> + app:layout_constraintTop_toBottomOf="@+id/userListSearch" /> - - - - - - - - + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_checkbox.xml b/vector/src/main/res/layout/item_checkbox.xml new file mode 100644 index 0000000000..c7427b46c8 --- /dev/null +++ b/vector/src/main/res/layout/item_checkbox.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_contact_action.xml b/vector/src/main/res/layout/item_contact_action.xml new file mode 100644 index 0000000000..daea0d5154 --- /dev/null +++ b/vector/src/main/res/layout/item_contact_action.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_user_list_header.xml b/vector/src/main/res/layout/item_user_list_header.xml new file mode 100644 index 0000000000..26591b68ca --- /dev/null +++ b/vector/src/main/res/layout/item_user_list_header.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/motion_dms_fab_menu_merge.xml b/vector/src/main/res/layout/motion_dms_fab_menu_merge.xml deleted file mode 100644 index c0bcec6cb3..0000000000 --- a/vector/src/main/res/layout/motion_dms_fab_menu_merge.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1eb602e4c3..b6f2e15ab2 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -79,6 +79,7 @@ Pause Dismiss Reset + Start Chatting @@ -1754,6 +1755,7 @@ View the room directory Name or ID (#example:matrix.org) + Name or ID (like %s) Enable swipe to reply in timeline Add a dedicated tab for unread notifications on main screen. @@ -1762,10 +1764,15 @@ Add by matrix ID Add by QR code + QR code "Creating room…" "No result found, use Add by matrix ID to search on server." "Start typing to get results" "Filter by username or ID…" + Recent + Known Users + Contacts + Suggestions "Joining room…" @@ -2540,14 +2547,22 @@ INVITE Inviting users… Invite Users + Invite Friends + Hey, Talk to me on Element: %s + 🔐️ Join me on element Invitation sent to %1$s Invitations sent to %1$s and %2$s + "It's not a valid matrix QR code" Invitations sent to %1$s and one more Invitations sent to %1$s and %2$d more We could not invite users. Please check the users you want to invite and try again. + Scan + My code + This is your matrix.to code. If you share it with someone they can scan it with their element camera to add you as a contact + Current language Other available languages Loading available languages… @@ -2672,15 +2687,17 @@ Can\'t open a room where you are banned from. Can\'t find this room. Make sure it exists. + + Share by text + Cannot DM yourself! + Invalid QR code (Invalid URI)! + QR code not scanned! + The link was malformed The room is not yet created. Cancel the room creation? There are unsaved changes. Discard the changes? Discard changes - - Share by text - Cannot DM yourself! - Invalid QR code (Invalid URI)! - QR code not scanned! + Matrix Link diff --git a/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml b/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml deleted file mode 100644 index 8bb1c55df5..0000000000 --- a/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From b888d13e62b0c4270dbb77938f3065c08f31f3eb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Nov 2020 11:12:23 +0100 Subject: [PATCH 121/231] Use orEmpty() --- .../crypto/verification/DefaultVerificationService.kt | 2 +- .../session/room/timeline/DefaultTimelineService.kt | 2 +- .../sdk/internal/session/sync/RoomTypingUsersHandler.kt | 2 +- .../internal/session/typing/DefaultTypingUsersTracker.kt | 2 +- .../android/sdk/internal/session/widgets/WidgetManager.kt | 2 +- .../app/features/roomdirectory/RoomDirectoryViewModel.kt | 6 +++--- .../vector/app/features/userdirectory/UserListController.kt | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 29ddd92213..a92f5c5bf1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -1204,7 +1204,7 @@ internal class DefaultVerificationService @Inject constructor( Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices") val targetDevices = otherDevices ?: cryptoStore.getUserDevices(otherUserId) - ?.values?.map { it.deviceId } ?: emptyList() + ?.values?.map { it.deviceId }.orEmpty() val requestsForUser = pendingRequests.getOrPut(otherUserId) { mutableListOf() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index df2d238c05..783aa53ddf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -103,7 +103,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) .findAll() ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } } - ?: emptyList() + .orEmpty() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt index 1655e551f1..f4f3e6ce43 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt @@ -28,7 +28,7 @@ internal class RoomTypingUsersHandler @Inject constructor(@UserId private val us fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) { val roomMemberHelper = RoomMemberHelper(realm, roomId) - val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId } ?: emptyList() + val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty() val senderInfo = typingIds.map { userId -> val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(userId) SenderInfo( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt index 2b7ff2624a..c5c3fc4b59 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/typing/DefaultTypingUsersTracker.kt @@ -37,6 +37,6 @@ internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTrac } override fun getTypingUsers(roomId: String): List { - return typingUsers[roomId] ?: emptyList() + return typingUsers[roomId].orEmpty() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 22bdd2c6e4..329903f15b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -138,7 +138,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: ): LiveData> { val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) return Transformations.map(widgetsAccountData) { - it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes) ?: emptyList() + it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes).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 42b17b4dad..c58e255bcc 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 @@ -204,9 +204,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: Timber.w("Try to join an already joining room. Should not happen") return@withState } - val viaServers = state.roomDirectoryData.homeServer?.let { - listOf(it) - } ?: emptyList() + val viaServers = state.roomDirectoryData.homeServer + ?.let { listOf(it) } + .orEmpty() session.joinRoom(action.roomId, viaServers = viaServers, callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. 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 6cd76401fd..353505a35e 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 @@ -106,7 +106,7 @@ class UserListController @Inject constructor(private val session: Session, currentState.getSelectedMatrixId(), currentState.searchTerm, // to avoid showing twice same user in known and suggestions - currentState.knownUsers.invoke()?.map { it.userId } ?: emptyList() + currentState.knownUsers.invoke()?.map { it.userId }.orEmpty() ) } } From ae6de8fdf154831e2ae2d8ad1153a3405255d2a3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Nov 2020 15:14:14 +0100 Subject: [PATCH 122/231] Small cleanup --- .../debug/res/layout/activity_debug_menu.xml | 2 +- .../im/vector/app/core/epoxy/CheckBoxItem.kt | 1 - .../createdirect/CreateDirectRoomActivity.kt | 8 ++++---- .../features/matrixto/MatrixToBottomSheet.kt | 6 ++---- .../features/usercode/ScanUserCodeFragment.kt | 1 + .../features/usercode/ShowUserCodeFragment.kt | 1 - .../app/features/usercode/UserCodeActivity.kt | 20 +++++++++---------- .../usercode/UserCodeSharedViewModel.kt | 14 ++++++------- .../src/main/res/layout/activity_simple.xml | 9 +++------ ...rd.xml => bottom_sheet_matrix_to_card.xml} | 6 +----- .../main/res/layout/dialog_share_qr_code.xml | 2 +- .../main/res/layout/fragment_home_drawer.xml | 2 -- .../fragment_qr_code_scanner_with_button.xml | 1 + .../res/layout/fragment_user_code_show.xml | 5 ++--- vector/src/main/res/layout/item_checkbox.xml | 5 +++-- .../res/layout/item_verification_qr_code.xml | 2 +- 16 files changed, 37 insertions(+), 48 deletions(-) rename vector/src/main/res/layout/{fragment_matrix_to_card.xml => bottom_sheet_matrix_to_card.xml} (97%) diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index 458b44fd05..9a95085a07 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -72,7 +72,7 @@ android:id="@+id/debug_qr_code" android:layout_width="200dp" android:layout_height="200dp" - tools:src="@tools:sample/avatars" /> + tools:src="@drawable/ic_qr_code_add" /> diff --git a/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt index 4e53b293d3..2f32fafa9e 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt @@ -21,7 +21,6 @@ import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import com.google.android.material.checkbox.MaterialCheckBox import im.vector.app.R -import kotlinx.android.synthetic.main.vector_preference_push_rule.view.* @EpoxyModelClass(layout = R.layout.item_checkbox) abstract class CheckBoxItem : VectorEpoxyModel() { diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 2e21d04d06..95351afec8 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -84,11 +84,11 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac .observe() .subscribe { action -> when (action) { - UserListSharedAction.Close -> finish() - UserListSharedAction.GoBack -> onBackPressed() + UserListSharedAction.Close -> finish() + UserListSharedAction.GoBack -> onBackPressed() is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action) - UserListSharedAction.OpenPhoneBook -> openPhoneBook() - UserListSharedAction.AddByQrCode -> openAddByQrCode() + UserListSharedAction.OpenPhoneBook -> openPhoneBook() + UserListSharedAction.AddByQrCode -> openAddByQrCode() }.exhaustive } .disposeOnDestroy() 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 3f3706699f..91c09ef21a 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 @@ -23,7 +23,7 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.home.AvatarRenderer -import kotlinx.android.synthetic.main.fragment_matrix_to_card.* +import kotlinx.android.synthetic.main.bottom_sheet_matrix_to_card.* import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -41,7 +41,7 @@ class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottom private var interactionListener: InteractionListener? = null - override fun getLayoutResId() = R.layout.fragment_matrix_to_card + override fun getLayoutResId() = R.layout.bottom_sheet_matrix_to_card override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -56,8 +56,6 @@ class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottom } companion object { - const val ARGS = "MatrixToFragment.Args" - fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet { return MatrixToBottomSheet(matrixItem).apply { interactionListener = listener diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt index 8b4820b06d..60354db9c6 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt @@ -102,6 +102,7 @@ class ScanUserCodeFragment @Inject constructor() override fun onPause() { super.onPause() + userCodeScannerView.setResultHandler(null) // Stop camera on pause userCodeScannerView.stopCamera() } diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt index ab88f79bef..e812ca31bb 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt @@ -54,6 +54,5 @@ class ShowUserCodeFragment @Inject constructor( state.shareLink?.let { showUserCodeQRImage.setData(it) } showUserCodeCardNameText.setTextOrHide(state.matrixItem?.displayName) showUserCodeCardUserIdText.setTextOrHide(state.matrixItem?.id) - Unit } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 388dc220a8..0e7efa5b92 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -29,6 +29,7 @@ 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.commitTransaction import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.features.matrixto.MatrixToBottomSheet @@ -66,8 +67,8 @@ class UserCodeActivity sharedViewModel.selectSubscribe(this, UserCodeState::mode) { mode -> when (mode) { - UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) - UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) + UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) + UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) is UserCodeState.Mode.RESULT -> { showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) MatrixToBottomSheet.create(mode.matrixItem, this).show(supportFragmentManager, "MatrixToBottomSheet") @@ -77,11 +78,11 @@ class UserCodeActivity sharedViewModel.observeViewEvents { when (it) { - is UserCodeShareViewEvents.InviteFriend -> TODO() - UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) + is UserCodeShareViewEvents.InviteFriend -> TODO() + UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false - is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() + is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId) }.exhaustive } @@ -89,14 +90,13 @@ class UserCodeActivity private fun showFragment(fragmentClass: KClass, bundle: Bundle) { if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) { - supportFragmentManager.beginTransaction().let { - it.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) - it.replace(R.id.simpleFragmentContainer, + supportFragmentManager.commitTransaction { + setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) + replace(R.id.simpleFragmentContainer, fragmentClass.java, bundle, fragmentClass.simpleName ) - it.commit() } } } @@ -110,7 +110,7 @@ class UserCodeActivity UserCodeState.Mode.SHOW -> super.onBackPressed() is UserCodeState.Mode.RESULT, UserCodeState.Mode.SCAN -> sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) - } + }.exhaustive } override fun create(initialState: UserCodeState, args: Args) = diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 17dd97cffa..1d1283c269 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -84,9 +84,9 @@ class UserCodeSharedViewModel @AssistedInject constructor( override fun handle(action: UserCodeActions) { when (action) { - UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) - is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } - is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) } } @@ -138,9 +138,9 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen) viewModelScope.launch(Dispatchers.IO) { when (linkedId) { - is PermalinkData.RoomLink -> TODO() - is PermalinkData.UserLink -> { - var user = session.getUser(linkedId.userId) ?: awaitCallback> { + is PermalinkData.RoomLink -> TODO() + is PermalinkData.UserLink -> { + val user = session.getUser(linkedId.userId) ?: awaitCallback> { session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it) }.firstOrNull { it.userId == linkedId.userId } // Create raw Uxid in case the user is not searchable @@ -152,7 +152,7 @@ class UserCodeSharedViewModel @AssistedInject constructor( ) } } - is PermalinkData.GroupLink -> TODO() + is PermalinkData.GroupLink -> TODO() is PermalinkData.FallbackLink -> TODO() } _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) diff --git a/vector/src/main/res/layout/activity_simple.xml b/vector/src/main/res/layout/activity_simple.xml index c6f2af8171..d7382d173d 100644 --- a/vector/src/main/res/layout/activity_simple.xml +++ b/vector/src/main/res/layout/activity_simple.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - + android:layout_height="40dp" /> - + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_matrix_to_card.xml b/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml similarity index 97% rename from vector/src/main/res/layout/fragment_matrix_to_card.xml rename to vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml index 7356f105e0..b8c81ded3a 100644 --- a/vector/src/main/res/layout/fragment_matrix_to_card.xml +++ b/vector/src/main/res/layout/bottom_sheet_matrix_to_card.xml @@ -1,12 +1,10 @@ - - - + app:layout_constraintTop_toBottomOf="@id/matrixToCardUserIdText" /> diff --git a/vector/src/main/res/layout/dialog_share_qr_code.xml b/vector/src/main/res/layout/dialog_share_qr_code.xml index d209376edb..04613023a7 100644 --- a/vector/src/main/res/layout/dialog_share_qr_code.xml +++ b/vector/src/main/res/layout/dialog_share_qr_code.xml @@ -11,6 +11,6 @@ android:layout_height="300dp" android:layout_gravity="center_horizontal" android:contentDescription="@string/a11y_qr_code_for_verification" - tools:src="@color/riotx_header_panel_background_black" /> + tools:src="@drawable/ic_qr_code_add" /> diff --git a/vector/src/main/res/layout/fragment_home_drawer.xml b/vector/src/main/res/layout/fragment_home_drawer.xml index 2f1d7cc4c4..459f118ccd 100644 --- a/vector/src/main/res/layout/fragment_home_drawer.xml +++ b/vector/src/main/res/layout/fragment_home_drawer.xml @@ -93,7 +93,6 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/homeDrawerUsernameView" /> - - + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_user_code_show.xml b/vector/src/main/res/layout/fragment_user_code_show.xml index 7d96b80dbe..92c40b1eb2 100644 --- a/vector/src/main/res/layout/fragment_user_code_show.xml +++ b/vector/src/main/res/layout/fragment_user_code_show.xml @@ -1,5 +1,4 @@ - - + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_checkbox.xml b/vector/src/main/res/layout/item_checkbox.xml index c7427b46c8..78dde9734b 100644 --- a/vector/src/main/res/layout/item_checkbox.xml +++ b/vector/src/main/res/layout/item_checkbox.xml @@ -1,13 +1,14 @@ \ No newline at end of file + app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer" + tools:text="@string/matrix_only_filter" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/item_verification_qr_code.xml b/vector/src/main/res/layout/item_verification_qr_code.xml index b57470fdc1..413b94013a 100644 --- a/vector/src/main/res/layout/item_verification_qr_code.xml +++ b/vector/src/main/res/layout/item_verification_qr_code.xml @@ -11,6 +11,6 @@ android:layout_height="200dp" android:layout_gravity="center_horizontal" android:contentDescription="@string/a11y_qr_code_for_verification" - tools:src="@color/riotx_header_panel_background_black" /> + tools:src="@drawable/ic_qr_code_add" /> From baef9f5aa7ac4af415b9f82438a9ec777f6c8193 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 20 Nov 2020 10:04:41 +0100 Subject: [PATCH 123/231] Fix permission handling + share my code by text --- .../app/core/platform/VectorBaseActivity.kt | 10 +++++ .../vector/app/core/utils/PermissionsTools.kt | 18 +++++++++ .../createdirect/CreateDirectRoomActivity.kt | 7 ++-- .../features/usercode/ScanUserCodeFragment.kt | 16 +++++++- .../features/usercode/ShowUserCodeFragment.kt | 31 +++++++++++++- .../app/features/usercode/UserCodeActions.kt | 2 + .../app/features/usercode/UserCodeActivity.kt | 17 ++++---- .../usercode/UserCodeShareViewEvents.kt | 3 +- .../usercode/UserCodeSharedViewModel.kt | 27 ++++++++----- vector/src/main/res/layout/activity.xml | 40 +++++++++++-------- .../res/layout/fragment_user_code_show.xml | 13 ++++++ vector/src/main/res/values/strings.xml | 2 + vector/src/main/res/values/style_snackbar.xml | 4 +- 13 files changed, 148 insertions(+), 42 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt index 79021902c4..f58f0b87ae 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt @@ -587,6 +587,16 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { } } + fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) { + coordinatorLayout?.let { + Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply { + withActionTitle?.let { + setAction(withActionTitle, { action?.invoke() }) + } + }.show() + } + } + /* ========================================================================================== * User Consent * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index 44fc6afa4e..f3ac8d9c47 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -19,8 +19,11 @@ package im.vector.app.core.utils import android.Manifest import android.app.Activity import android.content.Context +import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Build +import android.provider.Settings import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -30,6 +33,8 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import im.vector.app.R +import im.vector.app.core.platform.VectorBaseActivity +import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber // Android M permission request code management @@ -284,6 +289,19 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int, return isPermissionGranted } +fun VectorBaseActivity.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) { + showSnackbar(getString(rationaleMessage), R.string.settings) { + tryOrNull { + startActivity( + Intent().apply { + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + data = Uri.fromParts("package", this@onPermissionDeniedSnackbar.packageName, null) + }) + } + } +} + /** * Helper method used in [.checkPermissions] to populate the list of the * permissions to be granted (permissionsListToBeGrantedOut) and the list of the permissions already denied (permissionAlreadyDeniedListOut). diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 95351afec8..b4c278116b 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -22,7 +22,6 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail @@ -43,6 +42,7 @@ import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS import im.vector.app.core.utils.allGranted import im.vector.app.core.utils.checkPermissions +import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.features.contactsbook.ContactsBookFragment import im.vector.app.features.contactsbook.ContactsBookViewModel import im.vector.app.features.userdirectory.UserListFragment @@ -132,9 +132,10 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java) } } else { - Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show() if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) { - finish() + onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code) + } else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { + onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact) } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt index 60354db9c6..782d7e1c04 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt @@ -16,10 +16,13 @@ package im.vector.app.features.usercode +import android.Manifest import android.app.Activity +import android.content.pm.PackageManager import android.os.Bundle import android.view.View import android.widget.Toast +import androidx.core.content.ContextCompat import com.airbnb.mvrx.activityViewModel import com.google.zxing.Result import com.google.zxing.ResultMetadataType @@ -60,6 +63,9 @@ class ScanUserCodeFragment @Inject constructor() private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> if (allGranted) { startCamera() + } else { + // For now just go back + sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW)) } } @@ -90,12 +96,18 @@ class ScanUserCodeFragment @Inject constructor() } } + override fun onStart() { + super.onStart() + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + startCamera() + } + } + override fun onResume() { super.onResume() // Register ourselves as a handler for scan results. userCodeScannerView.setResultHandler(this) - // Start camera on resume - if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)) { startCamera() } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt index e812ca31bb..532557af84 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt @@ -23,6 +23,10 @@ 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.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.checkPermissions +import im.vector.app.core.utils.registerForPermissionsResult +import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.features.home.AvatarRenderer import kotlinx.android.synthetic.main.fragment_user_code_show.* import javax.inject.Inject @@ -35,13 +39,38 @@ class ShowUserCodeFragment @Inject constructor( val sharedViewModel: UserCodeSharedViewModel by activityViewModel() + private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> + if (allGranted) { + doOpenQRCodeScanner() + } else { + sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) showUserCodeClose.debouncedClicks { sharedViewModel.handle(UserCodeActions.DismissAction) } showUserCodeScanButton.debouncedClicks { - doOpenQRCodeScanner() + if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) { + doOpenQRCodeScanner() + } + } + shareByText.debouncedClicks { + sharedViewModel.handle(UserCodeActions.ShareByText) + } + + sharedViewModel.observeViewEvents { + if (it is UserCodeShareViewEvents.SharePlainText) { + startSharePlainTextIntent( + fragment = this, + activityResultLauncher = null, + chooserTitle = it.title, + text = it.text, + extraTitle = it.richPlainText + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt index 0611e0f8c3..3411fe3d7f 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt @@ -24,4 +24,6 @@ sealed class UserCodeActions : VectorViewModelAction { data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions() data class DecodedQRCode(val code: String) : UserCodeActions() data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions() + object CameraPermissionNotGranted : UserCodeActions() + object ShareByText : UserCodeActions() } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 0e7efa5b92..149caaba8f 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -32,6 +32,7 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.features.matrixto.MatrixToBottomSheet import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_simple.* @@ -78,13 +79,15 @@ class UserCodeActivity sharedViewModel.observeViewEvents { when (it) { - is UserCodeShareViewEvents.InviteFriend -> TODO() - UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) - UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true - UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false - is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() - is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId) - }.exhaustive + UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) + UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true + UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false + is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() + is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId) + UserCodeShareViewEvents.CameraPermissionNotGranted -> onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code) + else -> { + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt index 26fcffadd2..67a1ab8a6c 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt @@ -19,10 +19,11 @@ package im.vector.app.features.usercode import im.vector.app.core.platform.VectorViewEvents sealed class UserCodeShareViewEvents : VectorViewEvents { - data class InviteFriend(val permalink: String) : UserCodeShareViewEvents() object Dismiss : UserCodeShareViewEvents() object ShowWaitingScreen : UserCodeShareViewEvents() object HideWaitingScreen : UserCodeShareViewEvents() data class ToastMessage(val message: String) : UserCodeShareViewEvents() data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents() + object CameraPermissionNotGranted : UserCodeShareViewEvents() + data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 1d1283c269..b456f24972 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -71,12 +71,6 @@ class UserCodeSharedViewModel @AssistedInject constructor( } } - private fun handleInviteFriend() { - session.permalinkService().createPermalink(initialState.userId)?.let { permalink -> - _viewEvents.post(UserCodeShareViewEvents.InviteFriend(permalink)) - } - } - @AssistedInject.Factory interface Factory { fun create(initialState: UserCodeState, args: UserCodeActivity.Args): UserCodeSharedViewModel @@ -84,10 +78,23 @@ class UserCodeSharedViewModel @AssistedInject constructor( override fun handle(action: UserCodeActions) { when (action) { - UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) - is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } - is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) - is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted) + UserCodeActions.ShareByText -> handleShareByText() + } + } + + private fun handleShareByText() { + session.permalinkService().createPermalink(session.myUserId)?.let { permalink -> + val text = stringProvider.getString(R.string.invite_friends_text, permalink) + _viewEvents.post(UserCodeShareViewEvents.SharePlainText( + text, + stringProvider.getString(R.string.invite_friends), + stringProvider.getString(R.string.invite_friends_rich_title) + )) } } diff --git a/vector/src/main/res/layout/activity.xml b/vector/src/main/res/layout/activity.xml index b5203cd589..9e56d9e605 100644 --- a/vector/src/main/res/layout/activity.xml +++ b/vector/src/main/res/layout/activity.xml @@ -1,26 +1,32 @@ - - + android:layout_height="match_parent"> - + - + - + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_user_code_show.xml b/vector/src/main/res/layout/fragment_user_code_show.xml index 92c40b1eb2..ca65c64f19 100644 --- a/vector/src/main/res/layout/fragment_user_code_show.xml +++ b/vector/src/main/res/layout/fragment_user_code_show.xml @@ -49,6 +49,19 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/showUserCodeClose" + app:layout_constraintEnd_toStartOf="@id/shareByText" + app:layout_constraintTop_toTopOf="parent" /> + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b6f2e15ab2..d32dd86a19 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -425,6 +425,8 @@ Element can check your address book to find other Matrix users based on their email and phone numbers.\n\nDo you agree to share your address book for this purpose? Sorry. Action not performed, due to missing permissions + To scan a QR code, allow Camera permission to take a picture + To check your address book, allow Contact permission. Saved diff --git a/vector/src/main/res/values/style_snackbar.xml b/vector/src/main/res/values/style_snackbar.xml index 7e0dbc0e0b..5f1412e0b4 100644 --- a/vector/src/main/res/values/style_snackbar.xml +++ b/vector/src/main/res/values/style_snackbar.xml @@ -5,7 +5,9 @@ @color/notification_accent_color - - From 77863e2e88a40de5fe650afe3e16cddd606c036e Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 20 Nov 2020 14:49:24 +0100 Subject: [PATCH 128/231] copy tweaks --- vector/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 53ab08bffd..f82e7f6fe7 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2549,8 +2549,8 @@ INVITE Inviting users… Invite Users - Invite Friends - Hey, Talk to me on Element: %s + Invite friends + Hey, talk to me on Element: %s 🔐️ Join me on element Invitation sent to %1$s Invitations sent to %1$s and %2$s From 7583b0a3588af4b6e5d1393960ccd43037559ed4 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 20 Nov 2020 15:17:35 +0100 Subject: [PATCH 129/231] Fix rebase --- .../im/vector/app/features/userdirectory/UserListFragment.kt | 4 ++-- vector/src/main/res/layout/fragment_user_list.xml | 2 +- 2 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 4af16772b8..0aad6dbe51 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 @@ -93,7 +93,7 @@ class UserListFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + userListRecyclerView.cleanup() super.onDestroyView() } @@ -115,7 +115,7 @@ class UserListFragment @Inject constructor( private fun setupRecyclerView() { userListController.callback = this // Don't activate animation as we might have way to much item animation when filtering - recyclerView.configureWith(userListController, disableItemAnimation = true) + userListRecyclerView.configureWith(userListController, disableItemAnimation = true) } private fun setupSearchView() { diff --git a/vector/src/main/res/layout/fragment_user_list.xml b/vector/src/main/res/layout/fragment_user_list.xml index a5210ed99a..b2bb9a909c 100644 --- a/vector/src/main/res/layout/fragment_user_list.xml +++ b/vector/src/main/res/layout/fragment_user_list.xml @@ -122,7 +122,7 @@ tools:visibility="visible" /> Date: Fri, 20 Nov 2020 15:46:21 +0100 Subject: [PATCH 130/231] Fix string --- .../im/vector/app/features/userdirectory/UserListFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0aad6dbe51..4568878446 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 @@ -120,7 +120,7 @@ class UserListFragment @Inject constructor( private fun setupSearchView() { withState(viewModel) { - userListSearch.hint = getString(R.string.user_directory_search_hint, it.myUserId) + userListSearch.hint = getString(R.string.user_directory_search_hint) } userListSearch .textChanges() From d9757cc6600989886b38b9b21980c65b89d0dad1 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 20 Nov 2020 16:23:04 +0100 Subject: [PATCH 131/231] Fix lint --- vector/src/main/res/layout/item_contact_action.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_contact_action.xml b/vector/src/main/res/layout/item_contact_action.xml index 6c5fa38f4c..7a9a751257 100644 --- a/vector/src/main/res/layout/item_contact_action.xml +++ b/vector/src/main/res/layout/item_contact_action.xml @@ -1,5 +1,6 @@ Date: Fri, 20 Nov 2020 17:37:20 +0100 Subject: [PATCH 132/231] Better e2e log reporting --- .../internal/crypto/DefaultCryptoService.kt | 12 +++---- .../sdk/internal/crypto/EventDecryptor.kt | 18 ++++++---- .../crypto/IncomingGossipingRequestManager.kt | 7 ++-- .../sdk/internal/crypto/MXOlmDevice.kt | 2 +- .../sdk/internal/crypto/SendGossipWorker.kt | 2 +- .../EnsureOlmSessionsForDevicesAction.kt | 4 +-- .../algorithms/megolm/MXMegolmDecryption.kt | 22 +++++++------ .../algorithms/megolm/MXMegolmEncryption.kt | 33 +++++++++++-------- .../session/sync/CryptoSyncHandler.kt | 12 ++++++- 9 files changed, 69 insertions(+), 43 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index d3a3fd9fbd..ebd809f777 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -767,9 +767,9 @@ internal class DefaultCryptoService @Inject constructor( */ private fun onRoomKeyEvent(event: Event) { val roomKeyContent = event.getClearContent().toModel() ?: return - Timber.v("## CRYPTO | GOSSIP onRoomKeyEvent() : type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>") + Timber.i("## CRYPTO | onRoomKeyEvent() from: ${event.senderId} type<${event.getClearType()}> , sessionId<${roomKeyContent.sessionId}>") if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { - Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : missing fields") + Timber.e("## CRYPTO | onRoomKeyEvent() : missing fields") return } val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm) @@ -782,20 +782,20 @@ internal class DefaultCryptoService @Inject constructor( private fun onKeyWithHeldReceived(event: Event) { val withHeldContent = event.getClearContent().toModel() ?: return Unit.also { - Timber.e("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields") + Timber.i("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields") } - Timber.d("## CRYPTO | onKeyWithHeldReceived() received : content <$withHeldContent>") + Timber.i("## CRYPTO | onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>") val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm) if (alg is IMXWithHeldExtension) { alg.onRoomKeyWithHeldEvent(withHeldContent) } else { - Timber.e("## CRYPTO | onKeyWithHeldReceived() : Unable to handle WithHeldContent for ${withHeldContent.algorithm}") + Timber.e("## CRYPTO | onKeyWithHeldReceived() from:${event.senderId}: Unable to handle WithHeldContent for ${withHeldContent.algorithm}") return } } private fun onSecretSendReceived(event: Event) { - Timber.i("## CRYPTO | GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}") + Timber.i("## CRYPTO | GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}") if (!event.isEncrypted()) { // secret send messages must be encrypted Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index 38488f1ca7..92b7728890 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -119,7 +119,7 @@ internal class EventDecryptor @Inject constructor( markOlmSessionForUnwedging(event.senderId ?: "", it) } ?: run { - Timber.v("## CRYPTO | markOlmSessionForUnwedging() : Failed to find sender crypto device") + Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging") } } } @@ -137,16 +137,18 @@ internal class EventDecryptor @Inject constructor( val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0 val now = System.currentTimeMillis() if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) { - Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") + Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another") return } - Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") + Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}") lastNewSessionForcedDates.setObject(senderId, deviceKey, now) // offload this from crypto thread (?) cryptoCoroutineScope.launch(coroutineDispatchers.computation) { - ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) + val ensured = ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) + + Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}") // Now send a blank message on that session so the other side knows about it. // (The keyshare request is sent in the clear so that won't do) @@ -159,10 +161,14 @@ internal class EventDecryptor @Inject constructor( val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) val sendToDeviceMap = MXUsersDevicesMap() sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) - Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}") + Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}") withContext(coroutineDispatchers.io) { val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - sendToDeviceTask.execute(sendToDeviceParams) + try { + sendToDeviceTask.execute(sendToDeviceParams) + } catch (failure: Throwable) { + Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 97ae0b9d83..4f94a27bbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -54,6 +54,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( private val cryptoCoroutineScope: CoroutineScope) { private val executor = Executors.newSingleThreadExecutor() + // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations // we received in the current sync. private val receivedGossipingRequests = ArrayList() @@ -103,11 +104,11 @@ internal class IncomingGossipingRequestManager @Inject constructor( * @param event the announcement event. */ fun onGossipingRequestEvent(event: Event) { - Timber.v("## CRYPTO | GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}") val roomKeyShare = event.getClearContent().toModel() + Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare") // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } when (roomKeyShare?.action) { - GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { + GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { IncomingSecretShareRequest.fromEvent(event)?.let { if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { @@ -324,7 +325,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() when (secretName) { - MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master + MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index 1a4d1136c8..c952602d93 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -760,7 +760,7 @@ internal class MXOlmDevice @Inject constructor( return session } } else { - Timber.v("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") + Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index f0a3413978..bcaa16f356 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -100,7 +100,7 @@ internal class SendGossipWorker(context: Context, requestId = params.requestId, state = GossipingRequestState.FAILED_TO_ACCEPTED ) - Timber.e("no session with this device, probably because there were no one-time keys.") + Timber.e("no session with this device $requestingDeviceId, probably because there were no one-time keys.") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt index b05f2cd592..95b99c54e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt @@ -69,7 +69,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor( // // That should eventually resolve itself, but it's poor form. - Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") + Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams) @@ -90,7 +90,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor( oneTimeKey = key } if (oneTimeKey == null) { - Timber.v("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm + Timber.w("## CRYPTO | ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm + " for device " + userId + " : " + deviceId) continue } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index e0116fae1c..787d16defc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -243,8 +243,7 @@ internal class MXMegolmDecryption(private val userId: String, return } if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { - Timber.v("## CRYPTO | onRoomKeyEvent(), forward adding key : roomId ${roomKeyContent.roomId}" + - " sessionId ${roomKeyContent.sessionId} sessionKey ${roomKeyContent.sessionKey}") + Timber.i("## CRYPTO | onRoomKeyEvent(), forward adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") val forwardedRoomKeyContent = event.getClearContent().toModel() ?: return @@ -273,9 +272,7 @@ internal class MXMegolmDecryption(private val userId: String, keysClaimed["ed25519"] = forwardedRoomKeyContent.senderClaimedEd25519Key } else { - Timber.v("## CRYPTO | onRoomKeyEvent(), Adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId - + " sessionKey " + roomKeyContent.sessionKey) // from " + event); - + Timber.i("## CRYPTO | onRoomKeyEvent(), Adding key : ${roomKeyContent.roomId}|${roomKeyContent.sessionId}") if (null == senderKey) { Timber.e("## onRoomKeyEvent() : key event has no sender key (not encrypted?)") return @@ -285,7 +282,7 @@ internal class MXMegolmDecryption(private val userId: String, keysClaimed = event.getKeysClaimed().toMutableMap() } - Timber.e("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") + Timber.i("## CRYPTO | onRoomKeyEvent addInboundGroupSession ${roomKeyContent.sessionId}") val added = olmDevice.addInboundGroupSession(roomKeyContent.sessionId, roomKeyContent.sessionKey, roomKeyContent.roomId, @@ -349,10 +346,10 @@ internal class MXMegolmDecryption(private val userId: String, if (olmSessionResult?.sessionId == null) { // no session with this device, probably because there // were no one-time keys. + Timber.e("no session with this device $deviceId, probably because there were no one-time keys.") return@mapCatching } - Timber.v("## CRYPTO | shareKeysWithDevice() : sharing keys for session" + - " ${body.senderKey}|${body.sessionId} with device $userId:$deviceId") + Timber.i("## CRYPTO | shareKeysWithDevice() : sharing session ${body.sessionId} with device $userId:$deviceId") val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY) runCatching { olmDevice.getInboundGroupSession(body.sessionId, body.senderKey, body.roomId) } @@ -363,6 +360,7 @@ internal class MXMegolmDecryption(private val userId: String, }, { // TODO + Timber.e(it, "## CRYPTO | shareKeysWithDevice: failed to get session for request $body") } ) @@ -370,9 +368,13 @@ internal class MXMegolmDecryption(private val userId: String, val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) val sendToDeviceMap = MXUsersDevicesMap() sendToDeviceMap.setObject(userId, deviceId, encodedPayload) - Timber.v("## CRYPTO | shareKeysWithDevice() : sending to $userId:$deviceId") + Timber.i("## CRYPTO | shareKeysWithDevice() : sending ${body.sessionId} to $userId:$deviceId") val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - sendToDeviceTask.execute(sendToDeviceParams) + try { + sendToDeviceTask.execute(sendToDeviceParams) + } catch (failure: Throwable) { + Timber.e(failure, "## CRYPTO | shareKeysWithDevice() : Failed to send ${body.sessionId} to $userId:$deviceId") + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index e55cf37118..fd431ce735 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -217,8 +217,10 @@ internal class MXMegolmEncryption( Timber.v("## CRYPTO | shareUserDevicesKey() : starts") val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser) - Timber.v("## CRYPTO | shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after " - + (System.currentTimeMillis() - t0) + " ms") + Timber.v( + """## CRYPTO | shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms""" + .trimMargin() + ) val contentMap = MXUsersDevicesMap() var haveTargets = false val userIds = results.userIds @@ -242,7 +244,7 @@ internal class MXMegolmEncryption( continue } - Timber.v("## CRYPTO | shareUserDevicesKey() : Sharing keys with device $userId:$deviceID") + Timber.i("## CRYPTO | shareUserDevicesKey() : Sharing keys with device $userId:$deviceID") contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo))) haveTargets = true } @@ -270,21 +272,22 @@ internal class MXMegolmEncryption( if (haveTargets) { t0 = System.currentTimeMillis() - Timber.v("## CRYPTO | shareUserDevicesKey() : has target") + Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target") val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap) try { sendToDeviceTask.execute(sendToDeviceParams) - Timber.v("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms") + Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms") } catch (failure: Throwable) { // What to do here... Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ") } } else { - Timber.v("## CRYPTO | shareUserDevicesKey() : no need to sharekey") + Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey") } } private fun notifyKeyWithHeld(targets: List, sessionId: String, senderKey: String?, code: WithHeldCode) { + Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId ") val withHeldContent = RoomKeyWithHeldContent( roomId = roomId, senderKey = senderKey, @@ -393,16 +396,16 @@ internal class MXMegolmEncryption( userId: String, deviceId: String, senderKey: String): Boolean { - Timber.d("[MXMegolmEncryption] reshareKey: $sessionId to $userId:$deviceId") + Timber.i("## Crypto process reshareKey for $sessionId to $userId:$deviceId") val deviceInfo = cryptoStore.getUserDevice(userId, deviceId) ?: return false - .also { Timber.w("Device not found") } + .also { Timber.w("## Crypto reshareKey: Device not found") } // Get the chain index of the key we previously sent this device val chainIndex = outboundSession?.sharedWithHelper?.wasSharedWith(userId, deviceId) ?: return false .also { // Send a room key with held notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED) - Timber.w("[MXMegolmEncryption] reshareKey : ERROR : Never share megolm with this device") + Timber.w("## Crypto reshareKey: ERROR : Never share megolm with this device") } val devicesByUser = mapOf(userId to listOf(deviceInfo)) @@ -411,9 +414,11 @@ internal class MXMegolmEncryption( olmSessionResult?.sessionId ?: // no session with this device, probably because there were no one-time keys. // ensureOlmSessionsForDevicesAction has already done the logging, so just skip it. - return false + return false.also { + Timber.w("## Crypto reshareKey: no session with this device, probably because there were no one-time keys") + } - Timber.d("[MXMegolmEncryption] reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId") + Timber.i("[MXMegolmEncryption] reshareKey: sharing keys for session $senderKey|$sessionId:$chainIndex with device $userId:$deviceId") val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY) @@ -425,6 +430,7 @@ internal class MXMegolmEncryption( }, { // TODO + Timber.e(it, "[MXMegolmEncryption] reshareKey: failed to get session $sessionId|$senderKey|$roomId") } ) @@ -432,13 +438,14 @@ internal class MXMegolmEncryption( val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) val sendToDeviceMap = MXUsersDevicesMap() sendToDeviceMap.setObject(userId, deviceId, encodedPayload) - Timber.v("## CRYPTO | CRYPTO | reshareKey() : sending to $userId:$deviceId") + Timber.i("## CRYPTO | reshareKey() : sending session $sessionId to $userId:$deviceId") val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) return try { sendToDeviceTask.execute(sendToDeviceParams) + Timber.i("## CRYPTO reshareKey() : successfully send <$sessionId> to $userId:$deviceId") true } catch (failure: Throwable) { - Timber.e(failure, "## CRYPTO | CRYPTO | reshareKey() : fail to send <$sessionId> to $userId:$deviceId") + Timber.e(failure, "## CRYPTO reshareKey() : fail to send <$sessionId> to $userId:$deviceId") false } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt index da28199f1b..fc476a3dd6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult +import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.session.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.sync.model.SyncResponse @@ -39,6 +40,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService: toDevice.events?.forEachIndexed { index, event -> initialSyncProgressService?.reportProgress(((index / total.toFloat()) * 100).toInt()) // Decrypt event if necessary + Timber.i("## CRYPTO | To device event from ${event.senderId} of type:${event.type}") decryptToDeviceEvent(event, null) if (event.getClearType() == EventType.MESSAGE && event.getClearContent()?.toModel()?.msgType == "m.bad.encrypted") { @@ -69,7 +71,12 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService: result = cryptoService.decryptEvent(event, timelineId ?: "") } catch (exception: MXCryptoError) { event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError) - Timber.e("## CRYPTO | Failed to decrypt to device event: ${event.mCryptoError ?: exception}") + val senderKey = event.content.toModel()?.senderKey ?: "" + // try to find device id to ease log reading + val deviceId = cryptoService.getCryptoDeviceInfo(event.senderId!!).firstOrNull { + it.identityKey() == senderKey + }?.deviceId ?: senderKey + Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>") } if (null != result) { @@ -80,6 +87,9 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService: forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain ) return true + } else { + // should not happen + Timber.e("## CRYPTO | ERROR NULL DECRYPTION RESULT from ${event.senderId}") } } From d5523b18b9d4f9ea3540abd879dcf7c435bd99fa Mon Sep 17 00:00:00 2001 From: keraint Date: Fri, 20 Nov 2020 08:01:45 +0000 Subject: [PATCH 133/231] Translated using Weblate (French) Currently translated at 100.0% (1933 of 1933 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 26b09f1f2c..d6258d45bd 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2089,7 +2089,7 @@ Quitter Actions d\'administrateur Les messages ici ne sont pas chiffrés de bout en bout. - Réagis avec : %s + A réagi avec : %s Sondage Si vous ne connaissez pas votre mot de passe, faites précédent et réinitialisez-le. Veuillez utiliser le format international (le numéro de téléphone doit commencer avec « + ») From bc6debea89a3c559ed0f02da4277989e80dec80c Mon Sep 17 00:00:00 2001 From: Ed Geraghty Date: Sun, 22 Nov 2020 23:13:01 +0000 Subject: [PATCH 134/231] Disable native dialler integration on jitsi widget Native dialler integration breaks the jitsi widget for those of us with non-vanilla telephony frameworks. Disabling this integration (as is default on the Jitsi Meet Android app) allows us to also join conference calls, with no adverse effect on those for whom it already works. Signed-off-by: Ed Geraghty --- .../vector/app/features/call/conference/VectorJitsiActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt index 1ab6fb6363..43b41f7b5a 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/VectorJitsiActivity.kt @@ -109,6 +109,7 @@ class VectorJitsiActivity : VectorBaseActivity(), JitsiMeetActivityInterface, Ji .setFeatureFlag("invite.enabled", false) .setFeatureFlag("add-people.enabled", false) .setFeatureFlag("video-share.enabled", false) + .setFeatureFlag("call-integration.enabled", false) .setRoom(viewState.confId) .setSubject(viewState.subject) .build() From b125b473669c29716b2b21ba18d9516f4338c355 Mon Sep 17 00:00:00 2001 From: Ed Geraghty Date: Mon, 23 Nov 2020 00:01:05 +0000 Subject: [PATCH 135/231] Update CHANGES.md Signed-off-by: Ed Geraghty --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 80bb2c66b8..9e823db140 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Bugfix 🐛: - Discard change dialog displayed by mistake when avatar has been updated - Try to fix cropped image in timeline (#2126) - Registration: annoying error message scares every new user when they add an email (#2391) + - Fix jitsi integration for those with non-vanilla dialler frameworks Translations 🗣: - From 62b703ca88d3b60963012ae2dc4a03568a391459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B8vbr=C3=B8tte=20Olsen?= Date: Tue, 24 Nov 2020 07:08:01 +0000 Subject: [PATCH 136/231] =?UTF-8?q?Added=20translation=20using=20Weblate?= =?UTF-8?q?=20(Norwegian=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastlane/metadata/android/de/changelogs/40100100.txt | 1 + fastlane/metadata/android/fa/changelogs/40100100.txt | 1 + fastlane/metadata/android/pt_BR/changelogs/40100100.txt | 1 + fastlane/metadata/android/sv/changelogs/40100100.txt | 1 + 4 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/de/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/pt_BR/changelogs/40100100.txt create mode 100644 fastlane/metadata/android/sv/changelogs/40100100.txt diff --git a/fastlane/metadata/android/de/changelogs/40100100.txt b/fastlane/metadata/android/de/changelogs/40100100.txt new file mode 100644 index 0000000000..70b786d12e --- /dev/null +++ b/fastlane/metadata/android/de/changelogs/40100100.txt @@ -0,0 +1 @@ +// TODO diff --git a/fastlane/metadata/android/fa/changelogs/40100100.txt b/fastlane/metadata/android/fa/changelogs/40100100.txt new file mode 100644 index 0000000000..6123bfc7fc --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100100.txt @@ -0,0 +1 @@ +// برای انجام diff --git a/fastlane/metadata/android/pt_BR/changelogs/40100100.txt b/fastlane/metadata/android/pt_BR/changelogs/40100100.txt new file mode 100644 index 0000000000..02cfd45a87 --- /dev/null +++ b/fastlane/metadata/android/pt_BR/changelogs/40100100.txt @@ -0,0 +1 @@ +// A FAZER diff --git a/fastlane/metadata/android/sv/changelogs/40100100.txt b/fastlane/metadata/android/sv/changelogs/40100100.txt new file mode 100644 index 0000000000..6da756aca9 --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/40100100.txt @@ -0,0 +1 @@ +// ATT GÖRA From 0e908ad8829317c46b42f2bd58618ae1699705c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B8vbr=C3=B8tte=20Olsen?= Date: Tue, 24 Nov 2020 08:22:53 +0000 Subject: [PATCH 137/231] =?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 43.1% (834 of 1933 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 | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 42731a17c6..00b8c19bc4 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -590,7 +590,7 @@ Romkatalog Ingen offentlige rom tilgjengelig - 1 bruker + %d bruker %d brukere Send kjæsjlogg @@ -883,4 +883,29 @@ Du har ikke tillatelse til å starte en samtale i dette rommet Du har ikke tillatelse til å starte en konferansesamtale Tilbakestill + Ugyldig brukernavn/passord + Denne enheten bruker en utdatert TLS sikkerhetsprotokoll som er sårbar for angrep, for din egen sikkerhet får du ikke kople til serveren + Klarte ikke registrere bruker + Klarte ikke registrere bruker: Nettverksfeil + Klarte ikke logge inn + Klarte ikke logge inn: Nettverksfeil + Vennligst les og aksepter reglene for denne hjemmetjeneren: + Passordet ditt har blitt tilbakestilt. +\n +\nAlle øktene dine har blitt logget ut og får ikke lenger push-notifikasjoner. For å skru på notifikasjoner igjen, logg inn på enhetene på nytt. + Klarte ikke verifisere e-postadressen: Pass på at du har klikket på lenken i e-posten + En e-post har blitt sendt til %s. Etter du har fulgt lenken i den kan du klikke nedenfor. + Du må skrive et nytt passord. + Du må skrive inn e-postadressen som er knyttet til din konto. + For å tilbakestille passordet, skriv inn e-postadressen som er knyttet til kontoen din: + Denne hjemmetjeneren vil vite om du er en robot + Registrering med e-post og telefonummer samtidig fungerer ikke enda. Bare telefonummeret kommer til å bli registrert. +\n +\nDu kan legge e-postadressen din til i instillinger. + Bruk egendefinerte tjenerinstillinger (avansert) + Klarte ikke å starte en sanntidskopling. +\nVennligst be hjemmetjeneradministratoren din om å sette opp en TURN server så samtaler blir mer stabile. + Vennligst be administratoren for hjemmetjeneren din (%1$s) til å sette opp en TURN tjener for at telefonsamtaler skal fungere ordentlig. +\n +\nAlternativt kan du prøve å bruke den offentlige tjeneren på %2$s, men dette vil ikke være like stabilt, og det vil dele IP-adressen din med den serveren. Du kan styre dette i instillinger. \ No newline at end of file From d2398a7abbde7b01eac5634b611305f05a171d80 Mon Sep 17 00:00:00 2001 From: notramo Date: Tue, 24 Nov 2020 18:38:27 +0000 Subject: [PATCH 138/231] Translated using Weblate (Hungarian) Currently translated at 83.1% (158 of 190 strings) Translation: Element Android/Element Android Sdk Translate-URL: https://translate.element.io/projects/element-android/element-sdk/hu/ --- .../src/main/res/values-hu/strings.xml | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-hu/strings.xml b/matrix-sdk-android/src/main/res/values-hu/strings.xml index 4aade76c55..49238ee8ff 100644 --- a/matrix-sdk-android/src/main/res/values-hu/strings.xml +++ b/matrix-sdk-android/src/main/res/values-hu/strings.xml @@ -22,10 +22,10 @@ %s hanghívást indított. %s fogadta a hívást. %s befejezte a hívást. - %1$s láthatóvá tette a jövőbeli előzményeket %2$s számára - az összes szobatag, onnantól, hogy meg lettek hívva. - az összes szobatag, onnantól, hogy csatlakoztak. - az összes szobatag. + %1$s láthatóvá tette a jövőbeli előzményeket %2$s + a szoba összes tagja számára, a meghívásuk időpontjától kezdve. + a szoba összes tagja számára, a csatlakozásuk időpontjától kezdve. + az összes szobatag számára. bárki. ismeretlen (%s). %1$s bekapcsolta a végpontok közötti titkosítást (%2$s) @@ -139,4 +139,40 @@ Létrehoztad a szobát Matricát küldtél. Képet küldtél. + Saját + Saját (%1$d) + Alapértelmezett + Moderátor + Admin + Ön megváltoztatta a %1$s kisalkalmazást + %1$s megváltoztatta a %2$s kisalkalmazást + Ön eltávolította a %1$s kisalkalmazást + %1$s eltávolította a %2$s kisalkalmazást + Ön hozzáadott egy %1$s kisalkalmazást + %1$s hozzáadott egy %2$s kisalkalmazást + Ön elfogadta a meghívót ehhez: %1$s + Ön visszavonta %1$s felhasználó meghívóját + %1$s visszavonta %2$s felhasználó meghívóját + Ön visszavonta %1$s felhasználó meghívóját + Ön meghívta %1$s felhasználót + %1$s meghívta %2$s felhasználót + Ön meghívót küldött %1$s felhasználónak, hogy csatlakozzon a szobához + Ön frissítette a saját profilját %1$s + Ön eltávolította a szoba képét + %1$s eltávolította a szoba képét + Ön eltávolította a szoba témáját + Ön eltávolította a szoba nevét + Ön videókonferencia kezdeményezését kérte + Ön frissítette ezt a szobát. + %s frissítette a szobát. + Ön frissítette ezt a szobát. + Ön bekapcsolta a végpontok közötti titkosítást (%1$s) + Ön elérhetővé tette a jövőbeni üzeneteket %1$s + Ön elérhetővé tette a jövőbeni üzeneteket %1$s + %1$s elérhetővé tette a jövőbeni üzeneteket %2$s + Ön megváltoztatta a szoba nevét erre: %1$s + Ön eltávolította a saját megjelenített nevét (%1$s volt) + Ön megváltoztatta a saját megjelenítési nevét erről: %1$s, erre: %2$s + Ön beállította a saját megjelenítési nevét erre: %1$s + Az ön meghívása \ No newline at end of file From a8b5a5227f9c00424021f7ecfda9a65b73cbf70c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 15:47:27 +0100 Subject: [PATCH 139/231] Code review update --- .../createdirect/CreateDirectRoomActivity.kt | 4 ++-- .../features/invite/InviteUsersToRoomActivity.kt | 4 ++-- .../app/features/usercode/UserCodeActivity.kt | 4 ++-- .../features/usercode/UserCodeSharedViewModel.kt | 13 ++++--------- .../vector/app/features/usercode/UserCodeState.kt | 4 ++++ .../app/features/userdirectory/UserListViewModel.kt | 7 +++---- .../app/features/userdirectory/UserListViewState.kt | 4 ++++ 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index b4c278116b..6fafe0b977 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -71,8 +71,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac injector.inject(this) } - override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel { - return userListViewModelFactory.create(initialState, args) + override fun create(initialState: UserListViewState): UserListViewModel { + return userListViewModelFactory.create(initialState) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt index 513fbb5d83..65f547a662 100644 --- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt @@ -68,8 +68,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fa injector.inject(this) } - override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel { - return userListViewModelFactory.create(initialState, args) + override fun create(initialState: UserListViewState): UserListViewModel { + return userListViewModelFactory.create(initialState) } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index 149caaba8f..ffef98d544 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -116,8 +116,8 @@ class UserCodeActivity }.exhaustive } - override fun create(initialState: UserCodeState, args: Args) = - viewModelFactory.create(initialState, args) + override fun create(initialState: UserCodeState) = + viewModelFactory.create(initialState) companion object { fun newIntent(context: Context, userId: String): Intent { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index b456f24972..aa1f882e78 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -41,7 +41,6 @@ import org.matrix.android.sdk.internal.util.awaitCallback class UserCodeSharedViewModel @AssistedInject constructor( @Assisted val initialState: UserCodeState, - @Assisted val args: UserCodeActivity.Args, private val session: Session, private val stringProvider: StringProvider, private val rawService: RawService) : VectorViewModel(initialState) { @@ -53,27 +52,23 @@ class UserCodeSharedViewModel @AssistedInject constructor( is FragmentViewModelContext -> viewModelContext.fragment as? Factory is ActivityViewModelContext -> viewModelContext.activity as? Factory } - return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface") - } - - override fun initialState(viewModelContext: ViewModelContext): UserCodeState? { - return UserCodeState(viewModelContext.args().userId) + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } } init { - val user = session.getUser(args.userId) + val user = session.getUser(initialState.userId) setState { copy( matrixItem = user?.toMatrixItem(), - shareLink = session.permalinkService().createPermalink(args.userId) + shareLink = session.permalinkService().createPermalink(initialState.userId) ) } } @AssistedInject.Factory interface Factory { - fun create(initialState: UserCodeState, args: UserCodeActivity.Args): UserCodeSharedViewModel + fun create(initialState: UserCodeState): UserCodeSharedViewModel } override fun handle(action: UserCodeActions) { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt index 3be882af3d..fdcfa2cb5e 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt @@ -30,4 +30,8 @@ data class UserCodeState( object SCAN : Mode() data class RESULT(val matrixItem: MatrixItem) : Mode() } + + constructor(args: UserCodeActivity.Args) : this( + userId = args.userId + ) } 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 b1d9e26afa..85701c25a1 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 @@ -43,7 +43,6 @@ private typealias KnownUsersSearch = String private typealias DirectoryUsersSearch = String class UserListViewModel @AssistedInject constructor(@Assisted initialState: UserListViewState, - @Assisted args: UserListFragmentArgs, private val session: Session) : VectorViewModel(initialState) { @@ -54,7 +53,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User @AssistedInject.Factory interface Factory { - fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel + fun create(initialState: UserListViewState): UserListViewModel } companion object : MvRxViewModelFactory { @@ -65,7 +64,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User is ActivityViewModelContext -> viewModelContext.activity as? Factory } val args = viewModelContext.args() - return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface") + return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } } @@ -73,7 +72,7 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User setState { copy( myUserId = session.myUserId, - existingRoomId = args.existingRoomId + existingRoomId = initialState.existingRoomId ) } observeUsers() diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt index f7cf421ca8..69135f912d 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt @@ -35,6 +35,10 @@ data class UserListViewState( val existingRoomId: String? = null ) : MvRxState { + constructor(args: UserListFragmentArgs) : this( + existingRoomId = args.existingRoomId + ) + fun getSelectedMatrixId(): List { return pendingInvitees .mapNotNull { From bcd86977d2ff2ab6e52993fd96875dc7e9e477bf Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 17:17:48 +0100 Subject: [PATCH 140/231] Fix test + lint --- .../java/im/vector/app/ui/UiAllScreensSanityTest.kt | 5 ++++- .../vector/app/features/usercode/UserCodeSharedViewModel.kt | 1 - .../vector/app/features/userdirectory/UserListViewModel.kt | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index 1c05a5d529..6b30700116 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -310,7 +310,10 @@ class UiAllScreensSanityTest { clickOn(R.id.createChatRoomButton) withIdlingResource(activityIdlingResource(CreateDirectRoomActivity::class.java)) { - assertDisplayed(R.id.addByMatrixId) + onView(withId(R.id.userListRecyclerView)) + .perform(waitForView(withText(R.string.qr_code))) + onView(withId(R.id.userListRecyclerView)) + .perform(waitForView(withText(R.string.invite_friends))) } closeSoftKeyboard() diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index aa1f882e78..abef15a1ba 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -47,7 +47,6 @@ class UserCodeSharedViewModel @AssistedInject constructor( companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: UserCodeState): UserCodeSharedViewModel? { - val args = viewModelContext.args() val factory = when (viewModelContext) { is FragmentViewModelContext -> viewModelContext.fragment as? Factory is ActivityViewModelContext -> viewModelContext.activity as? Factory 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 85701c25a1..f8eabbaed0 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 @@ -63,7 +63,6 @@ class UserListViewModel @AssistedInject constructor(@Assisted initialState: User is FragmentViewModelContext -> viewModelContext.fragment as? Factory is ActivityViewModelContext -> viewModelContext.activity as? Factory } - val args = viewModelContext.args() return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface") } } From 804afc9a1d59a4dbf76a3fa03f52f303484ecad2 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 15:19:49 +0100 Subject: [PATCH 141/231] Fix issues with matrix.to deep linking --- vector/src/main/AndroidManifest.xml | 6 +- .../vector/app/features/home/HomeActivity.kt | 61 +++++- .../app/features/matrixto/MatrixToAction.kt | 24 +++ .../features/matrixto/MatrixToBottomSheet.kt | 118 +++++++++-- .../matrixto/MatrixToBottomSheetState.kt | 27 +++ .../matrixto/MatrixToBottomSheetViewModel.kt | 192 ++++++++++++++++++ .../features/matrixto/MatrixToViewEvents.kt | 24 +++ .../permalink/PermalinkHandlerActivity.kt | 31 +-- .../app/features/usercode/UserCodeActivity.kt | 7 +- .../usercode/UserCodeSharedViewModel.kt | 45 ++-- .../layout/bottom_sheet_matrix_to_card.xml | 34 +++- 11 files changed, 511 insertions(+), 58 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 7d2ca11813..e9bd03cb4b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -81,7 +81,8 @@ android:resource="@xml/shortcuts" /> - + - + - 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 32a4af1b1b..e3e942fce9 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 @@ -38,8 +38,12 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.utils.toast import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.notifications.NotificationDrawerManager +import im.vector.app.features.permalink.NavigationInterceptor +import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.popup.DefaultVectorAlert import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert @@ -50,10 +54,12 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState import im.vector.app.push.fcm.FcmHelper +import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.matrix.android.sdk.api.session.InitialSyncProgressService +import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.util.MatrixItem import timber.log.Timber import javax.inject.Inject @@ -64,7 +70,8 @@ data class HomeActivityArgs( val accountCreation: Boolean ) : Parcelable -class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory, ServerBackupStatusViewModel.Factory { +class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDetectorSharedViewModel.Factory, ServerBackupStatusViewModel.Factory, + NavigationInterceptor { private lateinit var sharedActionViewModel: HomeSharedActionViewModel @@ -82,6 +89,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var shortcutsHandler: ShortcutsHandler @Inject lateinit var unknownDeviceViewModelFactory: UnknownDeviceDetectorSharedViewModel.Factory + @Inject lateinit var permalinkHandler: PermalinkHandler private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerStateChanged(newState: Int) { @@ -117,9 +125,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) } @@ -136,20 +144,42 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet homeActivityViewModel.observeViewEvents { when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) - is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) - HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() - is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() + is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } shortcutsHandler.observeRoomsAndBuildShortcuts() .disposeOnDestroy() + + if (isFirstCreation()) { + handleIntent(intent) + } + } + + private fun handleIntent(intent: Intent?) { + intent?.dataString?.let { deepLink -> + if (!deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) return@let + + permalinkHandler.launch(this, deepLink, + navigationInterceptor = this, + buildTask = true) + // .delay(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { isHandled -> + if (!isHandled) { + toast(R.string.permalink_malformed) + } + } + .disposeOnDestroy() + } } private fun renderState(state: HomeActivityViewState) { when (val status = state.initialSyncProgressServiceStatus) { - is InitialSyncProgressService.Status.Idle -> { + is InitialSyncProgressService.Status.Idle -> { waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { @@ -270,6 +300,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet if (intent?.getParcelableExtra(MvRx.KEY_ARG)?.clearNotification == true) { notificationDrawerManager.clearAllEvents() } + handleIntent(intent) } override fun onDestroy() { @@ -313,11 +344,11 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet bugReporter.openBugReportScreen(this, false) return true } - R.id.menu_home_filter -> { + R.id.menu_home_filter -> { navigator.openRoomsFiltering(this) return true } - R.id.menu_home_setting -> { + R.id.menu_home_setting -> { navigator.openSettings(this) return true } @@ -334,6 +365,18 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } + override fun navToMemberProfile(userId: String): Boolean { + val listener = object : MatrixToBottomSheet.InteractionListener { + override fun navigateToRoom(roomId: String) { + navigator.openRoom(this@HomeActivity, roomId) + } + } + // TODO check if there is already one?? + MatrixToBottomSheet.withUserId(userId, 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/matrixto/MatrixToAction.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt new file mode 100644 index 0000000000..e1c6800494 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToAction.kt @@ -0,0 +1,24 @@ +/* + * 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.matrixto + +import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.util.MatrixItem + +sealed class MatrixToAction : VectorViewModelAction { + data class StartChattingWithUser(val matrixItem: MatrixItem) : 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 91c09ef21a..41020ea404 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 @@ -17,23 +17,38 @@ package im.vector.app.features.matrixto import android.os.Bundle +import android.os.Parcelable import android.view.View +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +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.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.features.home.AvatarRenderer +import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.bottom_sheet_matrix_to_card.* -import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject -class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottomSheetDialogFragment() { +class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { + + @Parcelize + data class MatrixToArgs( + val matrixToLink: String?, + val userId: String? + ) : Parcelable @Inject lateinit var avatarRenderer: AvatarRenderer - interface InteractionListener { - fun didTapStartMessage(matrixItem: MatrixItem) - } + @Inject + lateinit var matrixToBottomSheetViewModelFactory: MatrixToBottomSheetViewModel.Factory override fun injectWith(injector: ScreenComponent) { injector.inject(this) @@ -43,21 +58,100 @@ class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottom override fun getLayoutResId() = R.layout.bottom_sheet_matrix_to_card + private val viewModel by fragmentViewModel(MatrixToBottomSheetViewModel::class) + + interface InteractionListener { + fun navigateToRoom(roomId: String) + } + + override fun invalidate() = withState(viewModel) { state -> + super.invalidate() + when (val item = state.matrixItem) { + Uninitialized -> { + matrixToCardContentLoading.isVisible = false + matrixToCardUserContentVisibility.isVisible = false + } + is Loading -> { + matrixToCardContentLoading.isVisible = true + matrixToCardUserContentVisibility.isVisible = false + } + is Success -> { + matrixToCardContentLoading.isVisible = false + matrixToCardUserContentVisibility.isVisible = true + matrixToCardNameText.setTextOrHide(item.invoke().displayName) + matrixToCardUserIdText.setTextOrHide(item.invoke().id) + avatarRenderer.render(item.invoke(), matrixToCardAvatar) + } + is Fail -> { + // TODO display some error copy? + dismiss() + } + } + + when (state.startChattingState) { + Uninitialized -> { + matrixToCardButtonLoading.isVisible = false + matrixToCardSendMessageButton.isVisible = false + } + is Success -> { + matrixToCardButtonLoading.isVisible = false + matrixToCardSendMessageButton.isVisible = true + } + is Fail -> { + matrixToCardButtonLoading.isVisible = false + matrixToCardSendMessageButton.isVisible = true + // TODO display some error copy? + dismiss() + } + is Loading -> { + matrixToCardButtonLoading.isVisible = true + matrixToCardSendMessageButton.isInvisible = true + } + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) matrixToCardSendMessageButton.debouncedClicks { - interactionListener?.didTapStartMessage(matrixItem) - dismiss() + withState(viewModel) { + it.matrixItem.invoke()?.let { item -> + viewModel.handle(MatrixToAction.StartChattingWithUser(item)) + } + } } - matrixToCardNameText.setTextOrHide(matrixItem.displayName) - matrixToCardUserIdText.setTextOrHide(matrixItem.id) - avatarRenderer.render(matrixItem, matrixToCardAvatar) + viewModel.observeViewEvents { + when (it) { + is MatrixToViewEvents.NavigateToRoom -> { + interactionListener?.navigateToRoom(it.roomId) + dismiss() + } + MatrixToViewEvents.Dismiss -> dismiss() + } + } } companion object { - fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet { - return MatrixToBottomSheet(matrixItem).apply { + fun withLink(matrixToLink: String, listener: InteractionListener?): MatrixToBottomSheet { + return MatrixToBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( + matrixToLink = matrixToLink, + userId = null + )) + } + interactionListener = listener + } + } + + fun withUserId(userId: String, listener: InteractionListener?): MatrixToBottomSheet { + return MatrixToBottomSheet().apply { + arguments = Bundle().apply { + putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( + matrixToLink = null, + userId = userId + )) + } interactionListener = listener } } 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 new file mode 100644 index 0000000000..0080b28c66 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.matrixto + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.api.util.MatrixItem + +data class MatrixToBottomSheetState( + val matrixItem: Async = Uninitialized, + val startChattingState: Async = Uninitialized +) : MvRxState 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 new file mode 100644 index 0000000000..06cae3218b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -0,0 +1,192 @@ +/* + * 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.matrixto + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.R +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.core.resources.StringProvider +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.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.profile.ProfileService +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.internal.util.awaitCallback + +class MatrixToBottomSheetViewModel @AssistedInject constructor( + @Assisted initialState: MatrixToBottomSheetState, + @Assisted val args: MatrixToBottomSheet.MatrixToArgs, + private val session: Session, + private val stringProvider: StringProvider, + private val rawService: RawService) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: MatrixToBottomSheetState, + args: MatrixToBottomSheet.MatrixToArgs): MatrixToBottomSheetViewModel + } + + init { + setState { + copy(matrixItem = Loading()) + } + viewModelScope.launch(Dispatchers.IO) { + resolveLink() + } + } + + private suspend fun resolveLink() { + when { + args.matrixToLink != null -> { + val linkedId = PermalinkParser.parse(args.matrixToLink) + if (linkedId is PermalinkData.FallbackLink) { + setState { + copy( + matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))), + startChattingState = Uninitialized + ) + } + return + } + + when (linkedId) { + is PermalinkData.UserLink -> { + val user = resolveUser(linkedId.userId) + setState { + copy( + matrixItem = Success(user.toMatrixItem()), + startChattingState = Success(Unit) + ) + } + } + is PermalinkData.RoomLink -> TODO() + is PermalinkData.GroupLink -> { + // not yet supported + } + is PermalinkData.FallbackLink -> { + } + } + } + args.userId != null -> { + val user = resolveUser(args.userId) + + setState { + copy( + matrixItem = Success(user.toMatrixItem()), + startChattingState = Success(Unit) + ) + } + } + else -> { + setState { + copy( + matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.unexpected_error))), + startChattingState = Uninitialized + ) + } + } + } + } + + private suspend fun resolveUser(userId: String): User { + return (session.getUser(userId) + ?: tryOrNull { + awaitCallback { + session.getProfile(userId, it) + } + }?.let { + User(userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) + } + // Create raw Uxid in case the user is not searchable + ?: User(userId, null, null)) + } + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? { + val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + val args: MatrixToBottomSheet.MatrixToArgs = viewModelContext.args() + + return fragment.matrixToBottomSheetViewModelFactory.create(state, args) + } + } + + override fun handle(action: MatrixToAction) { + when (action) { + is MatrixToAction.StartChattingWithUser -> handleStartChatting(action) + }.exhaustive + } + + private fun handleStartChatting(action: MatrixToAction.StartChattingWithUser) { + val mxId = action.matrixItem.id + val existing = session.getExistingDirectRoomWithUser(mxId) + if (existing != null) { + // navigate to this room + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(existing)) + } else { + setState { + copy(startChattingState = Loading()) + } + // we should create the room then navigate + viewModelScope.launch(Dispatchers.IO) { + val adminE2EByDefault = rawService.getElementWellknown(session.myUserId) + ?.isE2EByDefault() + ?: true + + val roomParams = CreateRoomParams() + .apply { + invitedUserIds.add(mxId) + setDirectMessage() + enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault + } + + val roomId = + try { + awaitCallback { session.createRoom(roomParams, it) }.also { + setState { + copy(startChattingState = Success(Unit)) + } + } + } catch (failure: Throwable) { + setState { + copy(startChattingState = Fail(Exception(stringProvider.getString(R.string.invite_users_to_room_failure)))) + } + return@launch + } + _viewEvents.post(MatrixToViewEvents.NavigateToRoom(roomId)) + } + } + } +} 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 new file mode 100644 index 0000000000..f9491fd361 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToViewEvents.kt @@ -0,0 +1,24 @@ +/* + * 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.matrixto + +import im.vector.app.core.platform.VectorViewEvents + +sealed class MatrixToViewEvents : VectorViewEvents { + data class NavigateToRoom(val roomId: String) : MatrixToViewEvents() + object Dismiss : MatrixToViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt index e005dd06c5..e8064aaec5 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandlerActivity.kt @@ -23,11 +23,9 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.utils.toast +import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.LoadingFragment import im.vector.app.features.login.LoginActivity -import io.reactivex.android.schedulers.AndroidSchedulers -import java.util.concurrent.TimeUnit import javax.inject.Inject class PermalinkHandlerActivity : VectorBaseActivity() { @@ -45,23 +43,28 @@ class PermalinkHandlerActivity : VectorBaseActivity() { if (isFirstCreation()) { replaceFragment(R.id.simpleFragmentContainer, LoadingFragment::class.java) } + handleIntent() + } + + private fun handleIntent() { // If we are not logged in, open login screen. // In the future, we might want to relaunch the process after login. if (!sessionHolder.hasActiveSession()) { startLoginActivity() return } - val uri = intent.dataString - permalinkHandler.launch(this, uri, buildTask = true) - .delay(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { isHandled -> - if (!isHandled) { - toast(R.string.permalink_malformed) - } - finish() - } - .disposeOnDestroy() + // We forward intent to HomeActivity (singleTask) to avoid the dueling app problem + // https://stackoverflow.com/questions/25884954/deep-linking-and-multiple-app-instances + intent.setClass(this, HomeActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + startActivity(intent) + + finish() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + handleIntent() } private fun startLoginActivity() { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index ffef98d544..d6279470ae 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -36,7 +36,6 @@ import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.features.matrixto.MatrixToBottomSheet import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_simple.* -import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject import kotlin.reflect.KClass @@ -72,7 +71,7 @@ class UserCodeActivity UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) is UserCodeState.Mode.RESULT -> { showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) - MatrixToBottomSheet.create(mode.matrixItem, this).show(supportFragmentManager, "MatrixToBottomSheet") + MatrixToBottomSheet.withUserId(mode.matrixItem.id, this).show(supportFragmentManager, "MatrixToBottomSheet") } } } @@ -104,8 +103,8 @@ class UserCodeActivity } } - override fun didTapStartMessage(matrixItem: MatrixItem) { - sharedViewModel.handle(UserCodeActions.StartChattingWithUser(matrixItem)) + override fun navigateToRoom(roomId: String) { + navigator.openRoom(this, roomId) } override fun onBackPressed() = withState(sharedViewModel) { diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index abef15a1ba..93b198f9d7 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -30,12 +30,15 @@ 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.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.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.util.awaitCallback @@ -72,12 +75,12 @@ class UserCodeSharedViewModel @AssistedInject constructor( override fun handle(action: UserCodeActions) { when (action) { - UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) - is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } - is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) - is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) + UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss) + is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } + is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) + is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted) - UserCodeActions.ShareByText -> handleShareByText() + UserCodeActions.ShareByText -> handleShareByText() } } @@ -139,13 +142,21 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen) viewModelScope.launch(Dispatchers.IO) { when (linkedId) { - is PermalinkData.RoomLink -> TODO() - is PermalinkData.UserLink -> { - val user = session.getUser(linkedId.userId) ?: awaitCallback> { - session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it) - }.firstOrNull { it.userId == linkedId.userId } - // Create raw Uxid in case the user is not searchable - ?: User(linkedId.userId, null, null) + is PermalinkData.RoomLink -> { + // not yet supported + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) + } + is PermalinkData.UserLink -> { + val user = session.getUser(linkedId.userId) + ?: tryOrNull { + awaitCallback { + session.getProfile(linkedId.userId, it) + } + }?.let { + User(linkedId.userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) + } + // Create raw Uxid in case the user is not searchable + ?: User(linkedId.userId, null, null) setState { copy( @@ -153,8 +164,14 @@ class UserCodeSharedViewModel @AssistedInject constructor( ) } } - is PermalinkData.GroupLink -> TODO() - is PermalinkData.FallbackLink -> TODO() + is PermalinkData.GroupLink -> { + // not yet supported + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) + } + is PermalinkData.FallbackLink -> { + // not yet supported + _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) + } } _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen) } 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 b8c81ded3a..d051bd7c98 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 @@ -3,13 +3,24 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:minHeight="200dp"> + + + + + + From 8e6e6736a37f3ec8d180b111493d40129ec3ffc5 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 17:57:18 +0100 Subject: [PATCH 142/231] Code review --- .../sdk/api/session/user/UserService.kt | 5 +++ .../session/user/DefaultUserService.kt | 31 ++++++++++++++ .../matrixto/MatrixToBottomSheetState.kt | 10 ++++- .../matrixto/MatrixToBottomSheetViewModel.kt | 40 ++++++++----------- .../usercode/UserCodeSharedViewModel.kt | 17 +++----- 5 files changed, 68 insertions(+), 35 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index 2cfc4b731f..ab85f979bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -35,6 +35,11 @@ interface UserService { */ fun getUser(userId: String): User? + /** + * Try to resolve user from known users, or using profile api + */ + fun resolveUser(userId: String, callback: MatrixCallback) + /** * Search list of users on server directory. * @param search the searched term diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt index d2eb7a14ef..1740956915 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt @@ -19,10 +19,13 @@ package org.matrix.android.sdk.internal.session.user import androidx.lifecycle.LiveData import androidx.paging.PagedList import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import org.matrix.android.sdk.internal.session.user.model.SearchUserTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -32,12 +35,40 @@ import javax.inject.Inject internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource, private val searchUserTask: SearchUserTask, private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, + private val getProfileInfoTask: GetProfileInfoTask, private val taskExecutor: TaskExecutor) : UserService { override fun getUser(userId: String): User? { return userDataSource.getUser(userId) } + override fun resolveUser(userId: String, callback: MatrixCallback) { + val known = getUser(userId) + if (known != null) { + callback.onSuccess(known) + } else { + val params = GetProfileInfoTask.Params(userId) + getProfileInfoTask + .configureWith(params) { + this.callback = object : MatrixCallback { + override fun onSuccess(data: JsonDict) { + callback.onSuccess( + User( + userId, + data[ProfileService.DISPLAY_NAME_KEY] as? String, + data[ProfileService.AVATAR_URL_KEY] as? String) + ) + } + + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + } + } + .executeBy(taskExecutor) + } + } + override fun getUserLive(userId: String): LiveData> { return userDataSource.getUserLive(userId) } 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 0080b28c66..9ec2071a94 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 @@ -22,6 +22,14 @@ import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.util.MatrixItem data class MatrixToBottomSheetState( + val userId: String? = null, + val deepLink: String? = null, val matrixItem: Async = Uninitialized, val startChattingState: Async = Uninitialized -) : MvRxState +) : MvRxState { + + constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( + userId = args.userId, + deepLink = args.matrixToLink + ) +} 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 06cae3218b..5f06bc6edd 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 @@ -39,24 +39,20 @@ 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.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.util.awaitCallback class MatrixToBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: MatrixToBottomSheetState, - @Assisted val args: MatrixToBottomSheet.MatrixToArgs, private val session: Session, private val stringProvider: StringProvider, private val rawService: RawService) : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { - fun create(initialState: MatrixToBottomSheetState, - args: MatrixToBottomSheet.MatrixToArgs): MatrixToBottomSheetViewModel + fun create(initialState: MatrixToBottomSheetState): MatrixToBottomSheetViewModel } init { @@ -64,14 +60,14 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( copy(matrixItem = Loading()) } viewModelScope.launch(Dispatchers.IO) { - resolveLink() + resolveLink(initialState) } } - private suspend fun resolveLink() { + private suspend fun resolveLink(initialState: MatrixToBottomSheetState) { when { - args.matrixToLink != null -> { - val linkedId = PermalinkParser.parse(args.matrixToLink) + initialState.deepLink != null -> { + val linkedId = PermalinkParser.parse(initialState.deepLink) if (linkedId is PermalinkData.FallbackLink) { setState { copy( @@ -92,7 +88,9 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - is PermalinkData.RoomLink -> TODO() + is PermalinkData.RoomLink -> { + // not yet supported + } is PermalinkData.GroupLink -> { // not yet supported } @@ -100,8 +98,8 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } } } - args.userId != null -> { - val user = resolveUser(args.userId) + initialState.userId != null -> { + val user = resolveUser(initialState.userId) setState { copy( @@ -110,7 +108,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - else -> { + else -> { setState { copy( matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.unexpected_error))), @@ -122,24 +120,20 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveUser(userId: String): User { - return (session.getUser(userId) - ?: tryOrNull { - awaitCallback { - session.getProfile(userId, it) + return tryOrNull { + awaitCallback { + session.resolveUser(userId, it) } - }?.let { - User(userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) } - // Create raw Uxid in case the user is not searchable - ?: User(userId, null, null)) + // Create raw user in case the user is not searchable + ?: User(userId, null, null) } companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: MatrixToBottomSheetState): MatrixToBottomSheetViewModel? { val fragment: MatrixToBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() - val args: MatrixToBottomSheet.MatrixToArgs = viewModelContext.args() - return fragment.matrixToBottomSheetViewModelFactory.create(state, args) + return fragment.matrixToBottomSheetViewModelFactory.create(state) } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 93b198f9d7..98acab147e 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -35,10 +35,8 @@ 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.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.util.awaitCallback @@ -147,15 +145,12 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } is PermalinkData.UserLink -> { - val user = session.getUser(linkedId.userId) - ?: tryOrNull { - awaitCallback { - session.getProfile(linkedId.userId, it) - } - }?.let { - User(linkedId.userId, it[ProfileService.DISPLAY_NAME_KEY] as? String, it[ProfileService.AVATAR_URL_KEY] as? String) - } - // Create raw Uxid in case the user is not searchable + val user = tryOrNull { + awaitCallback { + session.resolveUser(linkedId.userId, it) + } + } + // Create raw Uxid in case the user is not searchable ?: User(linkedId.userId, null, null) setState { From 4f5632b9163648aefd5edee7186e997d465856bc Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 25 Nov 2020 18:02:01 +0100 Subject: [PATCH 143/231] code review --- .../vector/app/features/home/HomeActivity.kt | 12 +++---- .../matrixto/MatrixToBottomSheetViewModel.kt | 35 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) 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 e3e942fce9..d0d10beb31 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 @@ -125,9 +125,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet .observe() .subscribe { sharedAction -> when (sharedAction) { - is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START) - is HomeActivitySharedAction.OpenGroup -> { + is HomeActivitySharedAction.OpenGroup -> { drawerLayout.closeDrawer(GravityCompat.START) replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) } @@ -144,9 +144,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet homeActivityViewModel.observeViewEvents { when (it) { is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it) - is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) - HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() - is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) + is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it) + HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush() + is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it) }.exhaustive } homeActivityViewModel.subscribe(this) { renderState(it) } @@ -179,7 +179,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet private fun renderState(state: HomeActivityViewState) { when (val status = state.initialSyncProgressServiceStatus) { - is InitialSyncProgressService.Status.Idle -> { + is InitialSyncProgressService.Status.Idle -> { waiting_view.isVisible = false } is InitialSyncProgressService.Status.Progressing -> { 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 5f06bc6edd..79af3684b9 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 @@ -121,11 +121,11 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( private suspend fun resolveUser(userId: String): User { return tryOrNull { - awaitCallback { - session.resolveUser(userId, it) - } - } - // Create raw user in case the user is not searchable + awaitCallback { + session.resolveUser(userId, it) + } + } + // Create raw user in case the user is not searchable ?: User(userId, null, null) } @@ -166,19 +166,18 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault } - val roomId = - try { - awaitCallback { session.createRoom(roomParams, it) }.also { - setState { - copy(startChattingState = Success(Unit)) - } - } - } catch (failure: Throwable) { - setState { - copy(startChattingState = Fail(Exception(stringProvider.getString(R.string.invite_users_to_room_failure)))) - } - return@launch - } + val roomId = try { + awaitCallback { session.createRoom(roomParams, it) } + } catch (failure: Throwable) { + setState { + copy(startChattingState = Fail(Exception(stringProvider.getString(R.string.invite_users_to_room_failure)))) + } + return@launch + } + setState { + // we can hide this button has we will navigate out + copy(startChattingState = Uninitialized) + } _viewEvents.post(MatrixToViewEvents.NavigateToRoom(roomId)) } } From 15d93c8aeb6e8ac10ab07bac4ec294c8a8fd8c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B8vbr=C3=B8tte=20Olsen?= Date: Wed, 25 Nov 2020 11:27:35 +0000 Subject: [PATCH 144/231] =?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 43.3% (837 of 1933 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 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index 00b8c19bc4..c6275dd936 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -908,4 +908,9 @@ Vennligst be administratoren for hjemmetjeneren din (%1$s) til å sette opp en TURN tjener for at telefonsamtaler skal fungere ordentlig. \n \nAlternativt kan du prøve å bruke den offentlige tjeneren på %2$s, men dette vil ikke være like stabilt, og det vil dele IP-adressen din med den serveren. Du kan styre dette i instillinger. + e-postlenken har ikke blitt trykket på enda + Dette brukernavnet er allerede brukt + Inneholdt ikke gyldig JSON + Ugyldig JSON + Biletten din ble ikke gjenkjent \ No newline at end of file From fa191136cc086fbc128df54dfd6de4e6647d14bb Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 26 Nov 2020 13:27:10 +0100 Subject: [PATCH 145/231] Home empty screen design update --- .../home/room/list/RoomListFragment.kt | 4 +- .../main/res/drawable-hdpi/empty_state_dm.png | Bin 0 -> 87381 bytes .../res/drawable-hdpi/empty_state_room.png | Bin 0 -> 87625 bytes .../main/res/drawable-mdpi/empty_state_dm.png | Bin 0 -> 16511 bytes .../res/drawable-mdpi/empty_state_room.png | Bin 0 -> 14352 bytes .../res/drawable-xxhdpi/empty_state_dm.png | Bin 0 -> 172804 bytes .../res/drawable-xxhdpi/empty_state_room.png | Bin 0 -> 180637 bytes .../src/main/res/layout-land/view_state.xml | 110 ++++++++++++++++++ vector/src/main/res/layout/view_state.xml | 47 +++++--- vector/src/main/res/values/strings.xml | 4 +- 10 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 vector/src/main/res/drawable-hdpi/empty_state_dm.png create mode 100644 vector/src/main/res/drawable-hdpi/empty_state_room.png create mode 100644 vector/src/main/res/drawable-mdpi/empty_state_dm.png create mode 100644 vector/src/main/res/drawable-mdpi/empty_state_room.png create mode 100644 vector/src/main/res/drawable-xxhdpi/empty_state_dm.png create mode 100644 vector/src/main/res/drawable-xxhdpi/empty_state_room.png create mode 100644 vector/src/main/res/layout-land/view_state.xml 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 f1d35a74d5..d3dcea10c2 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 @@ -309,13 +309,13 @@ class RoomListFragment @Inject constructor( RoomListDisplayMode.PEOPLE -> StateView.State.Empty( getString(R.string.room_list_people_empty_title), - ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_chat), + ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm), getString(R.string.room_list_people_empty_body) ) RoomListDisplayMode.ROOMS -> StateView.State.Empty( getString(R.string.room_list_rooms_empty_title), - ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_group), + ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), getString(R.string.room_list_rooms_empty_body) ) else -> diff --git a/vector/src/main/res/drawable-hdpi/empty_state_dm.png b/vector/src/main/res/drawable-hdpi/empty_state_dm.png new file mode 100644 index 0000000000000000000000000000000000000000..74e80f2a9c97949b9ca8ceee99d7403032ff4850 GIT binary patch literal 87381 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ႣKHP2{NikxZxbAtl@Q-}W^z5AC7>S{lMnJmSu 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 N002ovPDHLkV1kGN@*MyG literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-hdpi/empty_state_room.png b/vector/src/main/res/drawable-hdpi/empty_state_room.png new file mode 100644 index 0000000000000000000000000000000000000000..a75bee0ff8f46c4b3341f5fc6ac138e435c57da6 GIT binary patch literal 87625 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 literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-mdpi/empty_state_dm.png b/vector/src/main/res/drawable-mdpi/empty_state_dm.png new file mode 100644 index 0000000000000000000000000000000000000000..40b77b8c8e734a5fd1974213fb9a722b3fc1c6fd GIT binary patch literal 16511 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 Ih(W;r0x{7IH2?qr literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-mdpi/empty_state_room.png b/vector/src/main/res/drawable-mdpi/empty_state_room.png new file mode 100644 index 0000000000000000000000000000000000000000..ce8db022346f218dfb765fe4573a3311177f457b GIT binary patch literal 14352 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 z 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 z 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�R+h4_bQec@c(cATHyn19P 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.&TmaHT)VQSNq4& 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

o^@4L(5uj zGx(^NjkO(+`tm0q83ZxbpRN&NDUf7+I8`C`w*laH|pK_ZL)F9yDH8h zPENDh)-Om#2xh~Hium% z1?Co2g=Y)c)qrpOZJCB#TXDye44$Cd18(V8JLj#>F~^%rVa9j!i>_(^9OexRcP{M$ z|NZ5tGKYtGv4x{QefR?mhJ z)UbYfHZ_9R<#i6qVY4M3J_nyT6}%!H;)@5(PF657NfJRAeqq#c|KHIb#0L4iJ5`HD zT&=O3kg<$Vau>zb>5Hirs!d93UY`vPtxvCkj-=S*1&|6_WZTTNngiE#ZFQjNbYt3v3eg%?d-EZGna60 zhL8z;`F(1R+|7^yRgt`Qr)((lFuDDVHWg@Mp8|Pmv9HHtAP7w0aEo$0W8t?-dmK!n zgxH*2(7h#dbAwSl$|!e`h#x%66uqC(DYCckD18OtC_{V~qii-(=nsw1Uf@wgf0Ywe z-b@ge8c63J_4@M`LG^9VXIx&(>DfTl&zt%{93N3dGx+7`qVLLV(m&uC+cv<6Tf{1h zj-2j)AiLzplw2_4dH(tPS!<0sDYfw@j4zdlSyaWC(A6&v^`e_64-%$X2NAS?R2oJe z@u3zfvpV!};5Yu91};6Kphep*NP02eA*4A5t~&U_oJ*E4SLW;60~&UX4iR~LKLjxX z6daLqVRXN0$Q;78{iZ?X;QN++t=w&gT*&^bTqv}}sxB(=_lN4Q>X0cO4x_-_okV5)d3o>o`N`dpKgYXIA5%iZwd`xcY|DxUX3828Ee%>~ z!l&0+j@cClMQqab^>WA^Y#KilEn?00ufs&yM_%EQFr?=cO|Cpdj96B9ppiqrFSW>e zS)!!RZ_0Y#lU~FEE6(^Sc_pPs*scO`o z62|bgsO7$f$CxR(o|6@xQY5H|s&yR7g;pQ-l>O{ty;v4}!@y#2;rnF%_Y=IgG+ntL z1a|17qqX4#AVX&yo0`9)s~iR#rj<{kA9dw2reue!yx?y$nFbnU4&9oa;hlS_H0UH5 z)G0@3(w?l-d{T`9YDh*CYFD@nlCy^O#&c_r{cX@OGE$5)stdzZ?vzdimCYRq0^VP* zq&akR;u=N5o#FH(6`LLVj8TNrtPf~u8M`uz%GyuqXWy1|vs)NiR6chAFKz744R{WVM*R zE;@<#r~bm}QNR&9(wd&E9Ix^6t$_(d1zS>Qm_n$?}gJ$E}4t?6zeI)O1ilmo!jpqL)9SyM5_0i`QS)Tpk z7~R!*5Wf;{T4&Um`nPn=)w9L3|Ft@QN+biRz&)SBdKHQ$1i^hMTB%#P%*9XH(oiYPk(A23xrA_Uq? z)s6x}{MRahJr%_)2@8M@8-<1Hr%ZJn#x{x{5xy74G$U1zK;Lx924H8sC7Rvn1eXGwy-g?tSfv*m#ob%m+eOWa= zeQHkA@lsBm%*>9_H+WH1M|^{J=wbW??2P1;L{i6`bKr6|p&fJA=1PX+MRGtf=Vli3 z*0r;Cxb=eZ4bzv|9_b7+<)n@%)4LI-50{ILLM#8)2M-1g-aD8*T2~Ws+s+ybXBks% zRuY9<3B~J-qvHIIbI-ZkPBuSQKXf&uS_K*!Eit9>y#);9=nS++7)I;n>iBt5*pR_1 zXg!!vjQY*Ds;Jk;C`(C#58ZizKr!Nb9X=sERl;`~N%_}VZkso@v#pRKYN+KWN1 zbc~%zvCcnHpOuL{g-`Q`P`Lj{dBmOg*+zs9sEc~7p%A^8Y;2*|Y&Q~UUI3=Mxg9D? zc~tzM%thK@QKo=uX)9N7XvD3;e1!V38>qVF85@HH3+KL{&VFclG7PON{uF`7>Oc>x(

fM)|P_&1^rBvelHQz3y(6qP`~ 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%0DA7=x`vHDorcdhUhds@2isE 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index 2d11daadbc..e8c7b9c8ac 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -20,7 +20,9 @@ android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="vertical" - android:padding="@dimen/layout_horizontal_margin"> + android:padding="@dimen/layout_horizontal_margin" + android:visibility="gone" + tools:visibility="visible"> + + + app:layout_constraintTop_toBottomOf="@id/emptyImageView" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@string/room_list_people_empty_title" /> - + app:layout_constraintTop_toBottomOf="@+id/emptyTitleView" + app:layout_constraintVertical_chainStyle="packed" + tools:text="@string/room_list_people_empty_body" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index cbfb95dc85..9f2f473f94 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1641,9 +1641,9 @@ Welcome home! Catch up on unread messages here Conversations - Your direct message conversations will be displayed here + Your direct message conversations will be displayed here. Tap the + bottom right to start some. Rooms - Your rooms will be displayed here + Your rooms will be displayed here. Tap the + bottom right to find existing ones or start some of your own. Reactions Agree From 79caa4e510661b7d98ebbb7b5dad75f9d7723d41 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 26 Nov 2020 13:31:00 +0100 Subject: [PATCH 146/231] update change log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 9e823db140..9a4e63cf06 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: - Handle events of type "m.room.server_acl" (#890) - Room creation form: add advanced section to disable federation (#1314) - Move "Enable Encryption" from room setting screen to room profile screen (#2394) + - Home empty screens quick design update (#2347) Bugfix 🐛: - Fix crash on AttachmentViewer (#2365) From fbc3f47eebedf05a0c4094e65eccaadd3eb6e9ab Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 26 Nov 2020 14:17:47 +0100 Subject: [PATCH 147/231] Fix / update profile when no rooms --- CHANGES.md | 1 + .../session/profile/DefaultProfileService.kt | 12 +++++++----- .../sdk/internal/session/user/UserStore.kt | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9e823db140..04050e70c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ Bugfix 🐛: - Try to fix cropped image in timeline (#2126) - Registration: annoying error message scares every new user when they add an email (#2391) - Fix jitsi integration for those with non-vanilla dialler frameworks + - Update profile has no effect if user is in zero rooms Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 8d09277295..5265e4f17d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -31,6 +31,7 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.database.model.UserThreePidEntity import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.content.FileUploader +import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback @@ -49,6 +50,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto private val finalizeAddingThreePidTask: FinalizeAddingThreePidTask, private val deleteThreePidTask: DeleteThreePidTask, private val pendingThreePidMapper: PendingThreePidMapper, + private val userStore: UserStore, private val fileUploader: FileUploader) : ProfileService { override fun getDisplayName(userId: String, matrixCallback: MatrixCallback>): Cancelable { @@ -70,17 +72,17 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto } override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback): Cancelable { - return setDisplayNameTask - .configureWith(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.io, matrixCallback) { + setDisplayNameTask.execute(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) + userStore.updateDisplayName(userId, newDisplayName) + } } override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg") setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) + userStore.updateAvatar(userId, response.contentUri) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserStore.kt index 5c8cbd08b1..c030872dad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserStore.kt @@ -18,12 +18,15 @@ package org.matrix.android.sdk.internal.session.user import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.internal.database.model.UserEntity +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject internal interface UserStore { suspend fun createOrUpdate(userId: String, displayName: String? = null, avatarUrl: String? = null) + suspend fun updateAvatar(userId: String, avatarUrl: String? = null) + suspend fun updateDisplayName(userId: String, displayName: String? = null) } internal class RealmUserStore @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : UserStore { @@ -34,4 +37,20 @@ internal class RealmUserStore @Inject constructor(@SessionDatabase private val m it.insertOrUpdate(userEntity) } } + + override suspend fun updateAvatar(userId: String, avatarUrl: String?) { + monarchy.awaitTransaction { realm -> + UserEntity.where(realm, userId).findFirst()?.let { + it.avatarUrl = avatarUrl ?: "" + } + } + } + + override suspend fun updateDisplayName(userId: String, displayName: String?) { + monarchy.awaitTransaction { realm -> + UserEntity.where(realm, userId).findFirst()?.let { + it.displayName = displayName ?: "" + } + } + } } From 835a36986da73ea41db70bc95f900ad3c9b4d7e8 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 26 Nov 2020 17:39:00 +0100 Subject: [PATCH 148/231] Refactoring following review --- .../vector/app/features/home/HomeActivity.kt | 5 +- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../features/matrixto/MatrixToBottomSheet.kt | 18 +----- .../matrixto/MatrixToBottomSheetState.kt | 4 +- .../matrixto/MatrixToBottomSheetViewModel.kt | 63 +++++++------------ .../features/permalink/PermalinkHandler.kt | 7 ++- .../app/features/usercode/UserCodeActivity.kt | 2 +- .../usercode/UserCodeSharedViewModel.kt | 2 +- .../app/features/usercode/UserCodeState.kt | 2 +- 9 files changed, 36 insertions(+), 69 deletions(-) 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 d0d10beb31..7dde0edf32 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 @@ -18,6 +18,7 @@ package im.vector.app.features.home import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.view.MenuItem @@ -365,14 +366,14 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet } } - override fun navToMemberProfile(userId: String): Boolean { + override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { val listener = object : MatrixToBottomSheet.InteractionListener { override fun navigateToRoom(roomId: String) { navigator.openRoom(this@HomeActivity, roomId) } } // TODO check if there is already one?? - MatrixToBottomSheet.withUserId(userId, listener) + MatrixToBottomSheet.withLink(deepLink.toString(), listener) .show(supportFragmentManager, "HA#MatrixToBottomSheet") return true } 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 e289234e7a..3f5e476a5e 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 @@ -1460,7 +1460,7 @@ class RoomDetailFragment @Inject constructor( return false } - override fun navToMemberProfile(userId: String): Boolean { + override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { openRoomMemberProfile(userId) return true } 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 41020ea404..69f30bb470 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 @@ -41,8 +41,7 @@ class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { @Parcelize data class MatrixToArgs( - val matrixToLink: String?, - val userId: String? + val matrixToLink: String ) : Parcelable @Inject lateinit var avatarRenderer: AvatarRenderer @@ -136,20 +135,7 @@ class MatrixToBottomSheet : VectorBaseBottomSheetDialogFragment() { return MatrixToBottomSheet().apply { arguments = Bundle().apply { putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( - matrixToLink = matrixToLink, - userId = null - )) - } - interactionListener = listener - } - } - - fun withUserId(userId: String, listener: InteractionListener?): MatrixToBottomSheet { - return MatrixToBottomSheet().apply { - arguments = Bundle().apply { - putParcelable(MvRx.KEY_ARG, MatrixToBottomSheet.MatrixToArgs( - matrixToLink = null, - userId = userId + matrixToLink = matrixToLink )) } interactionListener = listener 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 9ec2071a94..9b1ce9fea8 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 @@ -22,14 +22,12 @@ import com.airbnb.mvrx.Uninitialized import org.matrix.android.sdk.api.util.MatrixItem data class MatrixToBottomSheetState( - val userId: String? = null, - val deepLink: String? = null, + val deepLink: String, val matrixItem: Async = Uninitialized, val startChattingState: Async = Uninitialized ) : MvRxState { constructor(args: MatrixToBottomSheet.MatrixToArgs) : this( - userId = args.userId, deepLink = args.matrixToLink ) } 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 79af3684b9..6e8a530c9a 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 @@ -65,42 +65,20 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveLink(initialState: MatrixToBottomSheetState) { - when { - initialState.deepLink != null -> { - val linkedId = PermalinkParser.parse(initialState.deepLink) - if (linkedId is PermalinkData.FallbackLink) { - setState { - copy( - matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))), - startChattingState = Uninitialized - ) - } - return - } - - when (linkedId) { - is PermalinkData.UserLink -> { - val user = resolveUser(linkedId.userId) - setState { - copy( - matrixItem = Success(user.toMatrixItem()), - startChattingState = Success(Unit) - ) - } - } - is PermalinkData.RoomLink -> { - // not yet supported - } - is PermalinkData.GroupLink -> { - // not yet supported - } - is PermalinkData.FallbackLink -> { - } - } + val permalinkData = PermalinkParser.parse(initialState.deepLink) + if (permalinkData is PermalinkData.FallbackLink) { + setState { + copy( + matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.permalink_malformed))), + startChattingState = Uninitialized + ) } - initialState.userId != null -> { - val user = resolveUser(initialState.userId) + return + } + when (permalinkData) { + is PermalinkData.UserLink -> { + val user = resolveUser(permalinkData.userId) setState { copy( matrixItem = Success(user.toMatrixItem()), @@ -108,13 +86,16 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( ) } } - else -> { - setState { - copy( - matrixItem = Fail(IllegalArgumentException(stringProvider.getString(R.string.unexpected_error))), - startChattingState = Uninitialized - ) - } + is PermalinkData.RoomLink -> { + // not yet supported + _viewEvents.post(MatrixToViewEvents.Dismiss) + } + is PermalinkData.GroupLink -> { + // not yet supported + _viewEvents.post(MatrixToViewEvents.Dismiss) + } + is PermalinkData.FallbackLink -> { + _viewEvents.post(MatrixToViewEvents.Dismiss) } } } 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 11c55f6a73..f1149d8990 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 @@ -63,13 +63,14 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .flatMap { permalinkData -> - handlePermalink(permalinkData, context, navigationInterceptor, buildTask) + handlePermalink(permalinkData, deepLink, context, navigationInterceptor, buildTask) } .onErrorReturnItem(false) } private fun handlePermalink( permalinkData: PermalinkData, + rawLink: Uri, context: Context, navigationInterceptor: NavigationInterceptor?, buildTask: Boolean @@ -96,7 +97,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti Single.just(true) } is PermalinkData.UserLink -> { - if (navigationInterceptor?.navToMemberProfile(permalinkData.userId) != true) { + if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) { navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask) } Single.just(true) @@ -175,7 +176,7 @@ interface NavigationInterceptor { /** * Return true if the navigation has been intercepted */ - fun navToMemberProfile(userId: String): Boolean { + fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { return false } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt index d6279470ae..547e2d939f 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt @@ -71,7 +71,7 @@ class UserCodeActivity UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY) is UserCodeState.Mode.RESULT -> { showFragment(ShowUserCodeFragment::class, Bundle.EMPTY) - MatrixToBottomSheet.withUserId(mode.matrixItem.id, this).show(supportFragmentManager, "MatrixToBottomSheet") + MatrixToBottomSheet.withLink(mode.rawLink, this).show(supportFragmentManager, "MatrixToBottomSheet") } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 98acab147e..45b6f0ee65 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -155,7 +155,7 @@ class UserCodeSharedViewModel @AssistedInject constructor( setState { copy( - mode = UserCodeState.Mode.RESULT(user.toMatrixItem()) + mode = UserCodeState.Mode.RESULT(user.toMatrixItem(), action.code) ) } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt index fdcfa2cb5e..c26da7c0a4 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt @@ -28,7 +28,7 @@ data class UserCodeState( sealed class Mode { object SHOW : Mode() object SCAN : Mode() - data class RESULT(val matrixItem: MatrixItem) : Mode() + data class RESULT(val matrixItem: MatrixItem, val rawLink: String) : Mode() } constructor(args: UserCodeActivity.Args) : this( From 92ceb0e8fb49a7ebfad946e3278c6d9db97566a9 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 18:59:40 +0000 Subject: [PATCH 149/231] Convert IntegrationManagerService to suspend functions Signed-off-by: Dominic Fischer --- .../IntegrationManagerService.kt | 11 ++----- .../DefaultIntegrationManagerService.kt | 14 ++++----- .../integrationmanager/IntegrationManager.kt | 31 +++++-------------- .../settings/VectorSettingsGeneralFragment.kt | 5 +-- .../RoomWidgetPermissionViewModel.kt | 27 ++++++---------- .../permissions/WidgetPermissionsHelper.kt | 5 +-- 6 files changed, 30 insertions(+), 63 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt index e27d81edb7..60af93888e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.integrationmanager -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This is the entry point to manage integration. You can grab an instance of this service through an active session. */ @@ -80,19 +77,17 @@ interface IntegrationManagerService { /** * Offers to enable or disable the integration. * @param enable the param to change - * @param callback the matrix callback to listen for result. * @return Cancelable */ - fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback): Cancelable + suspend fun setIntegrationEnabled(enable: Boolean) /** * Offers to allow or disallow a widget. * @param stateEventId the eventId of the state event defining the widget. * @param allowed the param to change - * @param callback the matrix callback to listen for result. * @return Cancelable */ - fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback): Cancelable + suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) /** * Returns true if the widget is allowed, false otherwise. @@ -105,7 +100,7 @@ interface IntegrationManagerService { * @param widgetType the widget type to check for * @param domain the domain to check for */ - fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback): Cancelable + suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) /** * Returns true if the widget domain is allowed, false otherwise. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt index 753e865b4a..482ecbd8d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt @@ -16,10 +16,8 @@ package org.matrix.android.sdk.internal.session.integrationmanager -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService -import org.matrix.android.sdk.api.util.Cancelable import javax.inject.Inject internal class DefaultIntegrationManagerService @Inject constructor(private val integrationManager: IntegrationManager) : IntegrationManagerService { @@ -44,20 +42,20 @@ internal class DefaultIntegrationManagerService @Inject constructor(private val return integrationManager.isIntegrationEnabled() } - override fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback): Cancelable { - return integrationManager.setIntegrationEnabled(enable, callback) + override suspend fun setIntegrationEnabled(enable: Boolean) { + return integrationManager.setIntegrationEnabled(enable) } - override fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback): Cancelable { - return integrationManager.setWidgetAllowed(stateEventId, allowed, callback) + override suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) { + return integrationManager.setWidgetAllowed(stateEventId, allowed) } override fun isWidgetAllowed(stateEventId: String): Boolean { return integrationManager.isWidgetAllowed(stateEventId) } - override fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback): Cancelable { - return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed, callback) + override suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) { + return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed) } override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt index df4e407415..ebd57ce657 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt @@ -20,15 +20,12 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.extensions.observeNotNull @@ -41,7 +38,6 @@ import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccoun import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import timber.log.Timber import javax.inject.Inject @@ -137,22 +133,17 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri return integrationProvisioningContent?.enabled ?: false } - fun setIntegrationEnabled(enable: Boolean, callback: MatrixCallback): Cancelable { + suspend fun setIntegrationEnabled(enable: Boolean) { val isIntegrationEnabled = isIntegrationEnabled() if (enable == isIntegrationEnabled) { - callback.onSuccess(Unit) - return NoOpCancellable + return } val integrationProvisioningContent = IntegrationProvisioningContent(enabled = enable) val params = UpdateUserAccountDataTask.IntegrationProvisioning(integrationProvisioningContent = integrationProvisioningContent) - return updateUserAccountDataTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return updateUserAccountDataTask.execute(params) } - fun setWidgetAllowed(stateEventId: String, allowed: Boolean, callback: MatrixCallback): Cancelable { + suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) { val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS) val currentContent = currentAllowedWidgets?.content?.toModel() val newContent = if (currentContent == null) { @@ -165,11 +156,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri currentContent.copy(widgets = allowedWidgets) } val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent) - return updateUserAccountDataTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return updateUserAccountDataTask.execute(params) } fun isWidgetAllowed(stateEventId: String): Boolean { @@ -178,7 +165,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri return currentContent?.widgets?.get(stateEventId) ?: false } - fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean, callback: MatrixCallback): Cancelable { + suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) { val currentAllowedWidgets = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_ALLOWED_WIDGETS) val currentContent = currentAllowedWidgets?.content?.toModel() val newContent = if (currentContent == null) { @@ -195,11 +182,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri currentContent.copy(native = nativeAllowedWidgets) } val params = UpdateUserAccountDataTask.AllowedWidgets(allowedWidgetsContent = newContent) - return updateUserAccountDataTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return updateUserAccountDataTask.execute(params) } fun isNativeWidgetDomainAllowed(widgetType: String, domain: String?): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index b1ccabfb76..5a7ceb4084 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -58,7 +58,6 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService @@ -214,7 +213,9 @@ class VectorSettingsGeneralFragment @Inject constructor( it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> // Disable it while updating the state, will be re-enabled by the account data listener. it.isEnabled = false - session.integrationManagerService().setIntegrationEnabled(newValue as Boolean, NoOpMatrixCallback()) + lifecycleScope.launch { + session.integrationManagerService().setIntegrationEnabled(newValue as Boolean) + } true } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt index cb40e5672b..eb588ec9ae 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/RoomWidgetPermissionViewModel.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.net.URL @@ -106,14 +105,11 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in if (state.permissionData()?.isWebviewWidget.orFalse()) { WidgetPermissionsHelper(integrationManagerService, widgetService).changePermission(state.roomId, widgetId, false) } else { - awaitCallback { - session.integrationManagerService().setNativeWidgetDomainAllowed( - state.permissionData.invoke()?.widget?.type?.preferred ?: "", - state.permissionData.invoke()?.widgetDomain ?: "", - false, - it - ) - } + session.integrationManagerService().setNativeWidgetDomainAllowed( + state.permissionData.invoke()?.widget?.type?.preferred ?: "", + state.permissionData.invoke()?.widgetDomain ?: "", + false + ) } } catch (failure: Throwable) { Timber.v("Failure revoking widget: ${state.widgetId}") @@ -131,14 +127,11 @@ class RoomWidgetPermissionViewModel @AssistedInject constructor(@Assisted val in if (state.permissionData()?.isWebviewWidget.orFalse()) { WidgetPermissionsHelper(integrationManagerService, widgetService).changePermission(state.roomId, widgetId, true) } else { - awaitCallback { - session.integrationManagerService().setNativeWidgetDomainAllowed( - state.permissionData.invoke()?.widget?.type?.preferred ?: "", - state.permissionData.invoke()?.widgetDomain ?: "", - true, - it - ) - } + session.integrationManagerService().setNativeWidgetDomainAllowed( + state.permissionData.invoke()?.widget?.type?.preferred ?: "", + state.permissionData.invoke()?.widgetDomain ?: "", + true + ) } } catch (failure: Throwable) { Timber.v("Failure allowing widget: ${state.widgetId}") diff --git a/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt b/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt index 871e73592d..5664609a99 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/permissions/WidgetPermissionsHelper.kt @@ -19,7 +19,6 @@ package im.vector.app.features.widgets.permissions import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.widgets.WidgetService -import org.matrix.android.sdk.internal.util.awaitCallback class WidgetPermissionsHelper(private val integrationManagerService: IntegrationManagerService, private val widgetService: WidgetService) { @@ -30,8 +29,6 @@ class WidgetPermissionsHelper(private val integrationManagerService: Integration widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.SENSITIVE) ).firstOrNull() val eventId = widget?.event?.eventId ?: return - awaitCallback { - integrationManagerService.setWidgetAllowed(eventId, allow, it) - } + integrationManagerService.setWidgetAllowed(eventId, allow) } } From a3a2c0a9a8f1c388e58f03409fb23665f33d2267 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 19:10:08 +0000 Subject: [PATCH 150/231] Convert UploadsService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/room/uploads/UploadsService.kt | 7 +------ .../session/room/uploads/DefaultUploadsService.kt | 13 ++----------- .../roomprofile/uploads/RoomUploadsViewModel.kt | 5 +---- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt index c3cc1eb9ee..e2462d007d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/uploads/UploadsService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.room.uploads -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * This interface defines methods to get event with uploads (= attachments) sent to a room. It's implemented at the room level. */ @@ -29,7 +26,5 @@ interface UploadsService { * @param numberOfEvents the expected number of events to retrieve. The result can contain less events. * @param since token to get next page, or null to get the first page */ - fun getUploads(numberOfEvents: Int, - since: String?, - callback: MatrixCallback): Cancelable + suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt index 824bd23c01..895f1cf50d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/DefaultUploadsService.kt @@ -18,17 +18,12 @@ package org.matrix.android.sdk.internal.session.room.uploads import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.uploads.GetUploadsResult import org.matrix.android.sdk.api.session.room.uploads.UploadsService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultUploadsService @AssistedInject constructor( @Assisted private val roomId: String, - private val taskExecutor: TaskExecutor, private val getUploadsTask: GetUploadsTask, private val cryptoService: CryptoService ) : UploadsService { @@ -38,11 +33,7 @@ internal class DefaultUploadsService @AssistedInject constructor( fun create(roomId: String): UploadsService } - override fun getUploads(numberOfEvents: Int, since: String?, callback: MatrixCallback): Cancelable { - return getUploadsTask - .configureWith(GetUploadsTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), numberOfEvents, since)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun getUploads(numberOfEvents: Int, since: String?): GetUploadsResult { + return getUploadsTask.execute(GetUploadsTask.Params(roomId, cryptoService.isRoomEncrypted(roomId), numberOfEvents, since)) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index 76b1a9e0c3..763eed5474 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.session.room.uploads.GetUploadsResult import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx @@ -90,9 +89,7 @@ class RoomUploadsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val result = awaitCallback { - room.getUploads(20, token, it) - } + val result = room.getUploads(20, token) token = result.nextToken From 27050b911ba2ef3a99ed960735319e76e3f4958a Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 13 Nov 2020 19:37:21 +0000 Subject: [PATCH 151/231] Convert TermsService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/terms/TermsService.kt | 16 ++++-------- .../session/terms/DefaultTermsService.kt | 26 +++++++------------ .../change/SetIdentityServerViewModel.kt | 5 +--- .../features/terms/ReviewTermsViewModel.kt | 21 +++++---------- 4 files changed, 23 insertions(+), 45 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt index 2d88125662..10ce0829d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/terms/TermsService.kt @@ -16,22 +16,16 @@ package org.matrix.android.sdk.api.session.terms -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - interface TermsService { enum class ServiceType { IntegrationManager, IdentityService } - fun getTerms(serviceType: ServiceType, - baseUrl: String, - callback: MatrixCallback): Cancelable + suspend fun getTerms(serviceType: ServiceType, baseUrl: String): GetTermsResponse - fun agreeToTerms(serviceType: ServiceType, - baseUrl: String, - agreedUrls: List, - token: String?, - callback: MatrixCallback): Cancelable + suspend fun agreeToTerms(serviceType: ServiceType, + baseUrl: String, + agreedUrls: List, + token: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 5eb97cee3a..41914cc799 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -17,11 +17,10 @@ package org.matrix.android.sdk.internal.session.terms import dagger.Lazy -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.terms.GetTermsResponse import org.matrix.android.sdk.api.session.terms.TermsService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.RetrofitFactory @@ -33,8 +32,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTe import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureTrailingSlash import okhttp3.OkHttpClient @@ -49,13 +46,11 @@ internal class DefaultTermsService @Inject constructor( private val getOpenIdTokenTask: GetOpenIdTokenTask, private val identityRegisterTask: IdentityRegisterTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val taskExecutor: TaskExecutor + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : TermsService { - override fun getTerms(serviceType: TermsService.ServiceType, - baseUrl: String, - callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + override suspend fun getTerms(serviceType: TermsService.ServiceType, + baseUrl: String): GetTermsResponse { + return withContext(coroutineDispatchers.main) { val url = buildUrl(baseUrl, serviceType) val termsResponse = executeRequest(null) { apiCall = termsAPI.getTerms("${url}terms") @@ -64,12 +59,11 @@ internal class DefaultTermsService @Inject constructor( } } - override fun agreeToTerms(serviceType: TermsService.ServiceType, - baseUrl: String, - agreedUrls: List, - token: String?, - callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + override suspend fun agreeToTerms(serviceType: TermsService.ServiceType, + baseUrl: String, + agreedUrls: List, + token: String?) { + withContext(coroutineDispatchers.main) { val url = buildUrl(baseUrl, serviceType) val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt index 9331f67812..0f07a0353f 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceError -import org.matrix.android.sdk.api.session.terms.GetTermsResponse import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.internal.util.awaitCallback import java.net.UnknownHostException @@ -117,9 +116,7 @@ class SetIdentityServerViewModel @AssistedInject constructor( private suspend fun checkTerms(baseUrl: String) { try { - val data = awaitCallback { - mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl, it) - } + val data = mxSession.getTerms(TermsService.ServiceType.IdentityService, baseUrl) // has all been accepted? val resp = data.serverResponse diff --git a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt index df822807ee..89d6e970cc 100644 --- a/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/terms/ReviewTermsViewModel.kt @@ -28,8 +28,6 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.terms.GetTermsResponse -import org.matrix.android.sdk.internal.util.awaitCallback import timber.log.Timber class ReviewTermsViewModel @AssistedInject constructor( @@ -94,15 +92,12 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { - session.agreeToTerms( - termsArgs.type, - termsArgs.baseURL, - agreedUrls, - termsArgs.token, - it - ) - } + session.agreeToTerms( + termsArgs.type, + termsArgs.baseURL, + agreedUrls, + termsArgs.token + ) _viewEvents.post(ReviewTermsViewEvents.Success) } catch (failure: Throwable) { Timber.e(failure, "Failed to agree to terms") @@ -122,9 +117,7 @@ class ReviewTermsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val data = awaitCallback { - session.getTerms(termsArgs.type, termsArgs.baseURL, it) - } + val data = session.getTerms(termsArgs.type, termsArgs.baseURL) val terms = data.serverResponse.getLocalizedTerms(action.preferredLanguageCode).map { Term(it.localizedUrl ?: "", it.localizedName ?: "", From 7c9b943733daf2dc97dd8dd1ae38fdd5601b62c5 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 27 Nov 2020 09:58:14 +0100 Subject: [PATCH 152/231] Try other asset strategy as lint not happy --- .../main/res/drawable-hdpi/empty_state_dm.png | Bin 87381 -> 0 bytes .../res/drawable-hdpi/empty_state_room.png | Bin 87625 -> 0 bytes .../main/res/drawable-mdpi/empty_state_dm.png | Bin 16511 -> 0 bytes .../res/drawable-mdpi/empty_state_room.png | Bin 14352 -> 0 bytes .../main/res/drawable-nodpi/empty_state_dm.png | Bin 0 -> 171049 bytes .../empty_state_room.png | Bin .../res/drawable-xxhdpi/empty_state_dm.png | Bin 172804 -> 0 bytes vector/src/main/res/layout/view_state.xml | 4 +++- 8 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 vector/src/main/res/drawable-hdpi/empty_state_dm.png delete mode 100644 vector/src/main/res/drawable-hdpi/empty_state_room.png delete mode 100644 vector/src/main/res/drawable-mdpi/empty_state_dm.png delete mode 100644 vector/src/main/res/drawable-mdpi/empty_state_room.png create mode 100644 vector/src/main/res/drawable-nodpi/empty_state_dm.png rename vector/src/main/res/{drawable-xxhdpi => drawable-nodpi}/empty_state_room.png (100%) delete mode 100644 vector/src/main/res/drawable-xxhdpi/empty_state_dm.png diff --git a/vector/src/main/res/drawable-hdpi/empty_state_dm.png b/vector/src/main/res/drawable-hdpi/empty_state_dm.png deleted file mode 100644 index 74e80f2a9c97949b9ca8ceee99d7403032ff4850..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87381 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ႣKHP2{NikxZxbAtl@Q-}W^z5AC7>S{lMnJmSu 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 N002ovPDHLkV1kGN@*MyG diff --git a/vector/src/main/res/drawable-hdpi/empty_state_room.png b/vector/src/main/res/drawable-hdpi/empty_state_room.png deleted file mode 100644 index a75bee0ff8f46c4b3341f5fc6ac138e435c57da6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87625 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 diff --git a/vector/src/main/res/drawable-mdpi/empty_state_dm.png b/vector/src/main/res/drawable-mdpi/empty_state_dm.png deleted file mode 100644 index 40b77b8c8e734a5fd1974213fb9a722b3fc1c6fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16511 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 Ih(W;r0x{7IH2?qr diff --git a/vector/src/main/res/drawable-mdpi/empty_state_room.png b/vector/src/main/res/drawable-mdpi/empty_state_room.png deleted file mode 100644 index ce8db022346f218dfb765fe4573a3311177f457b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14352 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 M85QYTNwcv34@l$%5dZ)H literal 0 HcmV?d00001 diff --git a/vector/src/main/res/drawable-xxhdpi/empty_state_room.png b/vector/src/main/res/drawable-nodpi/empty_state_room.png similarity index 100% rename from vector/src/main/res/drawable-xxhdpi/empty_state_room.png rename to vector/src/main/res/drawable-nodpi/empty_state_room.png diff --git a/vector/src/main/res/drawable-xxhdpi/empty_state_dm.png b/vector/src/main/res/drawable-xxhdpi/empty_state_dm.png deleted file mode 100644 index 2d3d727446adb5baa58b2376ede5e1aa6e80bd9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172804 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 iQmIrbl}dH(>i-V}diXZ<%W~HM0000 Date: Fri, 27 Nov 2020 10:24:07 +0100 Subject: [PATCH 153/231] Update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 2e0d764579..761d8a4634 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ Bugfix 🐛: - Registration: annoying error message scares every new user when they add an email (#2391) - Fix jitsi integration for those with non-vanilla dialler frameworks - Update profile has no effect if user is in zero rooms + - Fix issues with matrix.to deep linking (#2349) Translations 🗣: - From cce4d7d4d9e09d43ad98ab74d12b16bb5a5d6a85 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 11:09:13 +0100 Subject: [PATCH 154/231] Use style instead of duplicating the whole layout file --- .../src/main/res/layout-land/view_state.xml | 110 ------------------ vector/src/main/res/layout/view_state.xml | 1 + vector/src/main/res/values-land/styles.xml | 8 ++ vector/src/main/res/values/styles.xml | 4 + 4 files changed, 13 insertions(+), 110 deletions(-) delete mode 100644 vector/src/main/res/layout-land/view_state.xml create mode 100644 vector/src/main/res/values-land/styles.xml diff --git a/vector/src/main/res/layout-land/view_state.xml b/vector/src/main/res/layout-land/view_state.xml deleted file mode 100644 index def2496633..0000000000 --- a/vector/src/main/res/layout-land/view_state.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index 7d3f88a2c0..a6b0b25501 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -57,6 +57,7 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/styles.xml b/vector/src/main/res/values/styles.xml index 09f17a77b4..8cc1fe70d8 100644 --- a/vector/src/main/res/values/styles.xml +++ b/vector/src/main/res/values/styles.xml @@ -3,4 +3,8 @@ + \ No newline at end of file From 14c71d6c07c23a365f220f5c8230052d72c5ec16 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 11:50:54 +0100 Subject: [PATCH 155/231] Fix issue with too big icons --- .../app/core/extensions/ConstraintLayout.kt | 28 +++++++++++++++++++ .../im/vector/app/core/platform/StateView.kt | 12 +++++++- .../home/room/list/RoomListFragment.kt | 26 +++++++++-------- vector/src/main/res/layout/view_state.xml | 9 ++---- 4 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt diff --git a/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt b/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt new file mode 100644 index 0000000000..b1b30da156 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/extensions/ConstraintLayout.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.extensions + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet + +fun ConstraintLayout.updateConstraintSet(block: (ConstraintSet) -> Unit) { + ConstraintSet().let { + it.clone(this) + block.invoke(it) + it.applyTo(this) + } +} diff --git a/vector/src/main/java/im/vector/app/core/platform/StateView.kt b/vector/src/main/java/im/vector/app/core/platform/StateView.kt index 2af3235cdf..9ecb03cb15 100755 --- a/vector/src/main/java/im/vector/app/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/app/core/platform/StateView.kt @@ -21,8 +21,10 @@ import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import im.vector.app.R +import im.vector.app.core.extensions.updateConstraintSet import kotlinx.android.synthetic.main.view_state.view.* class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) @@ -31,7 +33,12 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? sealed class State { object Content : State() object Loading : State() - data class Empty(val title: CharSequence? = null, val image: Drawable? = null, val message: CharSequence? = null) : State() + data class Empty( + val title: CharSequence? = null, + val image: Drawable? = null, + val isBigImage: Boolean = false, + val message: CharSequence? = null + ) : State() data class Error(val message: CharSequence? = null) : State() } @@ -71,6 +78,9 @@ class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? is State.Loading -> Unit is State.Empty -> { emptyImageView.setImageDrawable(newState.image) + emptyView.updateConstraintSet { + it.constrainPercentHeight(R.id.emptyImageView, if (newState.isBigImage) 0.5f else 0.1f) + } emptyMessageView.text = newState.message emptyTitleView.text = newState.title } 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 d3dcea10c2..c9c9011de4 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 @@ -295,28 +295,30 @@ class RoomListFragment @Inject constructor( RoomListDisplayMode.NOTIFICATIONS -> { if (hasNoRoom) { StateView.State.Empty( - getString(R.string.room_list_catchup_welcome_title), - ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_catchup), - getString(R.string.room_list_catchup_welcome_body) + title = getString(R.string.room_list_catchup_welcome_title), + image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_catchup), + message = getString(R.string.room_list_catchup_welcome_body) ) } else { StateView.State.Empty( - getString(R.string.room_list_catchup_empty_title), - ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), - getString(R.string.room_list_catchup_empty_body)) + title = getString(R.string.room_list_catchup_empty_title), + image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), + message = getString(R.string.room_list_catchup_empty_body)) } } RoomListDisplayMode.PEOPLE -> StateView.State.Empty( - getString(R.string.room_list_people_empty_title), - ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm), - getString(R.string.room_list_people_empty_body) + title = getString(R.string.room_list_people_empty_title), + image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm), + isBigImage = true, + message = getString(R.string.room_list_people_empty_body) ) RoomListDisplayMode.ROOMS -> StateView.State.Empty( - getString(R.string.room_list_rooms_empty_title), - ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), - getString(R.string.room_list_rooms_empty_body) + title = getString(R.string.room_list_rooms_empty_title), + image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), + isBigImage = true, + message = getString(R.string.room_list_rooms_empty_body) ) else -> // Always display the content in this mode, because if the footer diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index a6b0b25501..11f176e405 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -34,7 +34,6 @@ android:textSize="16sp" tools:text="Une erreur est survenue" /> - - + tools:layout_constraintHeight_percent="0.5" + tools:src="@drawable/ic_search_no_results" /> - Date: Fri, 27 Nov 2020 15:02:08 +0300 Subject: [PATCH 156/231] Add changelog. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 761d8a4634..9b57903455 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Improvements 🙌: - Room creation form: add advanced section to disable federation (#1314) - Move "Enable Encryption" from room setting screen to room profile screen (#2394) - Improve Invite user screen (seamless search for matrix ID) + - Add Setting Item to Change PIN (#2462) Bugfix 🐛: - Fix crash on AttachmentViewer (#2365) From 89e7e28bfae672a4ea92d0ceb0677ddf7007381c Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 27 Nov 2020 15:22:31 +0300 Subject: [PATCH 157/231] Add settings item to change pin. --- vector/src/main/res/values/strings.xml | 2 ++ vector/src/main/res/xml/vector_settings_pin.xml | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f82e7f6fe7..1641617fb1 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2686,6 +2686,8 @@ Require PIN after 2 minutes PIN code is required after 2 minutes of not using Element. PIN code is required every time you open Element. + Change PIN + Change the current PIN, you will have to validate your current PIN first. Confirm PIN to disable PIN Can\'t open a room where you are banned from. Can\'t find this room. Make sure it exists. diff --git a/vector/src/main/res/xml/vector_settings_pin.xml b/vector/src/main/res/xml/vector_settings_pin.xml index 27eb275b09..20e240ee35 100644 --- a/vector/src/main/res/xml/vector_settings_pin.xml +++ b/vector/src/main/res/xml/vector_settings_pin.xml @@ -7,6 +7,12 @@ android:summary="@string/settings_security_pin_code_summary" android:title="@string/settings_security_pin_code_title" /> + + Date: Fri, 27 Nov 2020 13:53:19 +0100 Subject: [PATCH 158/231] Cleanup --- vector/src/main/java/im/vector/app/core/platform/StateView.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/platform/StateView.kt b/vector/src/main/java/im/vector/app/core/platform/StateView.kt index 9ecb03cb15..57f5a11a91 100755 --- a/vector/src/main/java/im/vector/app/core/platform/StateView.kt +++ b/vector/src/main/java/im/vector/app/core/platform/StateView.kt @@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable import android.util.AttributeSet import android.view.View import android.widget.FrameLayout -import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.extensions.updateConstraintSet From e3ca89a81b8aa0c946dab7d77749c983e36442b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 13:43:58 +0100 Subject: [PATCH 159/231] Fix issue when there is no display name --- vector/src/main/res/layout/fragment_user_code_show.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/fragment_user_code_show.xml b/vector/src/main/res/layout/fragment_user_code_show.xml index deab9775d7..39537fe6a5 100644 --- a/vector/src/main/res/layout/fragment_user_code_show.xml +++ b/vector/src/main/res/layout/fragment_user_code_show.xml @@ -92,14 +92,14 @@ + android:minWidth="300dp" + android:paddingTop="40dp"> Date: Fri, 27 Nov 2020 13:59:23 +0100 Subject: [PATCH 160/231] Prepare version 1.0.11 --- CHANGES.md | 11 +++-------- .../metadata/android/en-US/changelogs/40100110.txt | 2 ++ 2 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/40100110.txt diff --git a/CHANGES.md b/CHANGES.md index c3469f0661..b031e626eb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes in Element 1.0.11 (2020-XX-XX) +Changes in Element 1.0.11 (2020-11-27) =================================================== Features ✨: @@ -30,14 +30,9 @@ Bugfix 🐛: - Update profile has no effect if user is in zero rooms - Fix issues with matrix.to deep linking (#2349) -Translations 🗣: - - - SDK API changes ⚠️: - - AccountService now exposes suspendable function instead of using MatrixCallback (#2354). Note: We will incrementally migrate all the SDK API in a near future. - -Build 🧱: - - + - AccountService now exposes suspendable function instead of using MatrixCallback (#2354). + Note: We will incrementally migrate all the SDK API in a near future (#2449) Test: - Add `allScreensTest` to cover all screens of the app diff --git a/fastlane/metadata/android/en-US/changelogs/40100110.txt b/fastlane/metadata/android/en-US/changelogs/40100110.txt new file mode 100644 index 0000000000..e587003352 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40100110.txt @@ -0,0 +1,2 @@ +This new version mainly contains user interface and user experience improvements. Now you can invite friends, and create DM very fast by scanning QR codes. +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.11 \ No newline at end of file From 33a5cc1488324b9dfec1f7887de1f99659f21f34 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Fri, 27 Nov 2020 13:18:07 +0000 Subject: [PATCH 161/231] Remove redundant returns Signed-off-by: Dominic Fischer --- .../integrationmanager/DefaultIntegrationManagerService.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt index 482ecbd8d6..8bf6437009 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/DefaultIntegrationManagerService.kt @@ -43,11 +43,11 @@ internal class DefaultIntegrationManagerService @Inject constructor(private val } override suspend fun setIntegrationEnabled(enable: Boolean) { - return integrationManager.setIntegrationEnabled(enable) + integrationManager.setIntegrationEnabled(enable) } override suspend fun setWidgetAllowed(stateEventId: String, allowed: Boolean) { - return integrationManager.setWidgetAllowed(stateEventId, allowed) + integrationManager.setWidgetAllowed(stateEventId, allowed) } override fun isWidgetAllowed(stateEventId: String): Boolean { @@ -55,7 +55,7 @@ internal class DefaultIntegrationManagerService @Inject constructor(private val } override suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean) { - return integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed) + integrationManager.setNativeWidgetDomainAllowed(widgetType, domain, allowed) } override fun isNativeWidgetDomainAllowed(widgetType: String, domain: String): Boolean { From 163f4cfaf261efd8419418cdadacf81dba08ff75 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 15:15:56 +0100 Subject: [PATCH 162/231] Ensure the Activity is destroyed, it seems that the intent flags are not enough now. --- vector/src/main/java/im/vector/app/features/MainActivity.kt | 6 ++++-- .../im/vector/app/features/home/HomeActivityViewModel.kt | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index e553b5e0d3..f398a6ffa1 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -62,8 +62,8 @@ data class MainActivityArgs( ) : Parcelable /** - * This is the entry point of RiotX - * This Activity, when started with argument, is also doing some cleanup when user disconnects, + * This is the entry point of Element Android + * This Activity, when started with argument, is also doing some cleanup when user signs out, * clears cache, is logged out, or is soft logged out */ class MainActivity : VectorBaseActivity(), UnlockedActivity { @@ -78,6 +78,8 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity { intent.putExtra(EXTRA_ARGS, args) activity.startActivity(intent) + // Ensure the Activity is destroyed, it seems that the intent flags are not enough now. + activity.finish() } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 6d0bb7395b..680ec17415 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -91,7 +91,7 @@ class HomeActivityViewModel @AssistedInject constructor( val isVerified = it.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { // cross signing keys have been reset - // Tigger a popup to re-verify + // Trigger a popup to re-verify // Note: user can be null in case of logout safeActiveSession.getUser(safeActiveSession.myUserId) ?.toMatrixItem() From 245aa6e9e714cd55e54e9a613f57da3d8982f482 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Fri, 27 Nov 2020 17:17:24 +0300 Subject: [PATCH 163/231] Create a new pin when tap on change pin item. --- .../app/features/settings/VectorPreferences.kt | 1 + .../settings/VectorSettingsPinFragment.kt | 16 ++++++++++++++++ vector/src/main/res/values/strings.xml | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 5872c1fa1c..9d6ed0246c 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -165,6 +165,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { // Security const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE" const val SETTINGS_SECURITY_USE_PIN_CODE_FLAG = "SETTINGS_SECURITY_USE_PIN_CODE_FLAG" + const val SETTINGS_SECURITY_CHANGE_PIN_CODE_FLAG = "SETTINGS_SECURITY_CHANGE_PIN_CODE_FLAG" private const val SETTINGS_SECURITY_USE_BIOMETRICS_FLAG = "SETTINGS_SECURITY_USE_BIOMETRICS_FLAG" private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG" const val SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG = "SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG" diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index 37465258f6..94328dc44a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -21,6 +21,7 @@ import androidx.preference.Preference import androidx.preference.SwitchPreference import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.preference.VectorPreference import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.pin.PinCodeStore @@ -41,6 +42,10 @@ class VectorSettingsPinFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!! } + private val changePinCodePref by lazy { + findPreference(VectorPreferences.SETTINGS_SECURITY_CHANGE_PIN_CODE_FLAG)!! + } + private val useCompleteNotificationPref by lazy { findPreference(VectorPreferences.SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG)!! } @@ -74,6 +79,17 @@ class VectorSettingsPinFragment @Inject constructor( } true } + + changePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + if (hasPinCode) { + navigator.openPinCode( + requireContext(), + pinActivityResultLauncher, + PinMode.CREATE + ) + } + true + } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1641617fb1..bbb09e8df8 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2687,7 +2687,7 @@ PIN code is required after 2 minutes of not using Element. PIN code is required every time you open Element. Change PIN - Change the current PIN, you will have to validate your current PIN first. + Change your current PIN Confirm PIN to disable PIN Can\'t open a room where you are banned from. Can\'t find this room. Make sure it exists. From 7b969ebd749a701c263a106926bd0a4512548c74 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 16:00:30 +0100 Subject: [PATCH 164/231] Ensure the Activity is destroyed, it seems that the intent flags are not enough now. - finish all --- vector/src/main/java/im/vector/app/features/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index f398a6ffa1..8499b740f7 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -78,8 +78,8 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity { intent.putExtra(EXTRA_ARGS, args) activity.startActivity(intent) - // Ensure the Activity is destroyed, it seems that the intent flags are not enough now. - activity.finish() + // Ensure all the Activities are destroyed, it seems that the intent flags are not enough now. + activity.finishAffinity() } } From b84d7f0834d9853ad37878b31a9d219a0c28fb52 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 20:42:02 +0100 Subject: [PATCH 165/231] Version** --- CHANGES.md | 27 +++++++++++++++++++++++++++ vector/build.gradle | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b031e626eb..e48281081b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,30 @@ +Changes in Element 1.0.12 (2020-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Test: + - + +Other changes: + - + Changes in Element 1.0.11 (2020-11-27) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 037b049a76..561e1fd824 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -17,7 +17,7 @@ androidExtensions { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 0 -ext.versionPatch = 11 +ext.versionPatch = 12 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From cf70916764b3b5a3347e38333877c4256e7f8f80 Mon Sep 17 00:00:00 2001 From: Valere Date: Sat, 28 Nov 2020 00:41:29 +0100 Subject: [PATCH 166/231] Fix / double bottomsheet effect --- CHANGES.md | 2 +- .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../verification/QuadSLoadingFragment.kt | 25 +++++ .../crypto/verification/VerificationAction.kt | 1 + .../verification/VerificationBottomSheet.kt | 20 ++-- .../VerificationBottomSheetViewModel.kt | 94 ++++++++++++------- .../src/main/res/layout/fragment_progress.xml | 14 +++ 7 files changed, 118 insertions(+), 44 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt create mode 100644 vector/src/main/res/layout/fragment_progress.xml diff --git a/CHANGES.md b/CHANGES.md index e48281081b..5b250bcf1a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Double bottomsheet effect after verify with passphrase Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 32c98922fb..2c6a8225d8 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -36,6 +36,7 @@ import im.vector.app.features.crypto.recover.BootstrapMigrateBackupFragment import im.vector.app.features.crypto.recover.BootstrapSaveRecoveryKeyFragment import im.vector.app.features.crypto.recover.BootstrapSetupRecoveryKeyFragment import im.vector.app.features.crypto.recover.BootstrapWaitingFragment +import im.vector.app.features.crypto.verification.QuadSLoadingFragment import im.vector.app.features.crypto.verification.cancel.VerificationCancelFragment import im.vector.app.features.crypto.verification.cancel.VerificationNotMeFragment import im.vector.app.features.crypto.verification.choose.VerificationChooseMethodFragment @@ -418,6 +419,11 @@ interface FragmentModule { @FragmentKey(VerificationCancelFragment::class) fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment + @Binds + @IntoMap + @FragmentKey(QuadSLoadingFragment::class) + fun bindQuadSLoadingFragment(fragment: QuadSLoadingFragment): Fragment + @Binds @IntoMap @FragmentKey(VerificationNotMeFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt new file mode 100644 index 0000000000..a0ab1c86a7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/QuadSLoadingFragment.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.crypto.verification + +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseFragment +import javax.inject.Inject + +class QuadSLoadingFragment @Inject constructor() : VectorBaseFragment() { + override fun getLayoutResId() = R.layout.fragment_progress +} diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt index a32a9de97f..a5142ad8bf 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt @@ -31,5 +31,6 @@ sealed class VerificationAction : VectorViewModelAction { object SkipVerification : VerificationAction() object VerifyFromPassphrase : VerificationAction() data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction() + object CancelledFromSsss : VerificationAction() object SecuredStorageHasBeenReset : VerificationAction() } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index 35ea96de6f..f310a6e3a3 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -106,7 +106,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { viewModel.observeViewEvents { when (it) { - is VerificationBottomSheetViewEvents.Dismiss -> dismiss() + is VerificationBottomSheetViewEvents.Dismiss -> dismiss() is VerificationBottomSheetViewEvents.AccessSecretStore -> { secretStartForActivityResult.launch(SharedSecureStorageActivity.newIntent( requireContext(), @@ -115,7 +115,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS )) } - is VerificationBottomSheetViewEvents.ModalError -> { + is VerificationBottomSheetViewEvents.ModalError -> { AlertDialog.Builder(requireContext()) .setTitle(getString(R.string.dialog_title_error)) .setMessage(it.errorMessage) @@ -124,7 +124,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { .show() Unit } - VerificationBottomSheetViewEvents.GoToSettings -> { + VerificationBottomSheetViewEvents.GoToSettings -> { dismiss() (activity as? VectorBaseActivity)?.navigator?.openSettings(requireContext(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY) } @@ -155,6 +155,8 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { // all have been reset, so we are verified? viewModel.handle(VerificationAction.SecuredStorageHasBeenReset) } + } else { + viewModel.handle(VerificationAction.CancelledFromSsss) } } @@ -209,6 +211,10 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { return@withState } + if (state.selfVerificationMode && state.verifyingFrom4S) { + showFragment(QuadSLoadingFragment::class, Bundle()) + return@withState + } if (state.selfVerificationMode && state.verifiedFromPrivateKeys) { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) @@ -242,7 +248,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) }) } - is VerificationTxState.Verified -> { + is VerificationTxState.Verified -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) }) @@ -258,7 +264,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } when (state.qrTransactionState) { - is VerificationTxState.QrScannedByOther -> { + is VerificationTxState.QrScannedByOther -> { showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) return@withState } @@ -272,13 +278,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { }) return@withState } - is VerificationTxState.Verified -> { + is VerificationTxState.Verified -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) }) return@withState } - is VerificationTxState.Cancelled -> { + is VerificationTxState.Cancelled -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.qrTransactionState.cancelCode.value, state.isMe)) }) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index aa20a9a992..611853d48b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -32,6 +32,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session @@ -70,6 +71,7 @@ data class VerificationBottomSheetViewState( // true when we display the loading and we wait for the other (incoming request) val selfVerificationMode: Boolean = false, val verifiedFromPrivateKeys: Boolean = false, + val verifyingFrom4S: Boolean = false, val isMe: Boolean = false, val currentDeviceCanCrossSign: Boolean = false, val userWantsToCancel: Boolean = false, @@ -170,7 +172,9 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } } else { // if the verification is already done you can't cancel anymore - if (state.pendingRequest.invoke()?.cancelConclusion != null || state.sasTransactionState is VerificationTxState.TerminalTxState) { + if (state.pendingRequest.invoke()?.cancelConclusion != null + || state.sasTransactionState is VerificationTxState.TerminalTxState + || state.verifyingFrom4S) { // you cannot cancel anymore } else { setState { @@ -346,6 +350,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) } is VerificationAction.VerifyFromPassphrase -> { + setState { copy(verifyingFrom4S = true) } _viewEvents.post(VerificationBottomSheetViewEvents.AccessSecretStore) } is VerificationAction.GotResultFromSsss -> { @@ -354,56 +359,73 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( VerificationAction.SecuredStorageHasBeenReset -> { if (session.cryptoService().crossSigningService().allPrivateKeysKnown()) { setState { - copy(quadSHasBeenReset = true) + copy(quadSHasBeenReset = true, verifyingFrom4S = false) } } Unit } + VerificationAction.CancelledFromSsss -> { + setState { + copy(verifyingFrom4S = false) + } + } }.exhaustive } private fun handleSecretBackFromSSSS(action: VerificationAction.GotResultFromSsss) { - try { - action.cypherData.fromBase64().inputStream().use { ins -> - val res = session.loadSecureSecret>(ins, action.alias) - val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( - res?.get(MASTER_KEY_SSSS_NAME), - res?.get(USER_SIGNING_KEY_SSSS_NAME), - res?.get(SELF_SIGNING_KEY_SSSS_NAME) - ) - if (trustResult.isVerified()) { - // Sign this device and upload the signature - session.sessionParams.deviceId?.let { deviceId -> - session.cryptoService() - .crossSigningService().trustDevice(deviceId, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - Timber.w(failure, "Failed to sign my device after recovery") - } - }) - } + viewModelScope.launch(Dispatchers.IO) { + try { + action.cypherData.fromBase64().inputStream().use { ins -> + val res = session.loadSecureSecret>(ins, action.alias) + val trustResult = session.cryptoService().crossSigningService().checkTrustFromPrivateKeys( + res?.get(MASTER_KEY_SSSS_NAME), + res?.get(USER_SIGNING_KEY_SSSS_NAME), + res?.get(SELF_SIGNING_KEY_SSSS_NAME) + ) + if (trustResult.isVerified()) { + // Sign this device and upload the signature + session.sessionParams.deviceId?.let { deviceId -> + session.cryptoService() + .crossSigningService().trustDevice(deviceId, object : MatrixCallback { + override fun onFailure(failure: Throwable) { + Timber.w(failure, "Failed to sign my device after recovery") + } + }) + } - setState { - copy(verifiedFromPrivateKeys = true) - } + setState { + copy( + verifyingFrom4S = false, + verifiedFromPrivateKeys = true + ) + } - // try to get keybackup key - } else { - // POP UP something - _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys))) + // try the keybackup + tentativeRestoreBackup(res) + } else { + setState { + copy( + verifyingFrom4S = false + ) + } + // POP UP something + _viewEvents.post(VerificationBottomSheetViewEvents.ModalError(stringProvider.getString(R.string.error_failed_to_import_keys))) + } } - - // try the keybackup - tentativeRestoreBackup(res) - Unit + } catch (failure: Throwable) { + setState { + copy( + verifyingFrom4S = false + ) + } + _viewEvents.post( + VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))) } - } catch (failure: Throwable) { - _viewEvents.post( - VerificationBottomSheetViewEvents.ModalError(failure.localizedMessage ?: stringProvider.getString(R.string.unexpected_error))) } } private fun tentativeRestoreBackup(res: Map?) { - viewModelScope.launch(Dispatchers.IO) { + GlobalScope.launch(Dispatchers.IO) { try { val secret = res?.get(KEYBACKUP_SECRET_SSSS_NAME) ?: return@launch Unit.also { Timber.v("## Keybackup secret not restored from SSSS") @@ -460,7 +482,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } when (tx) { - is SasVerificationTransaction -> { + is SasVerificationTransaction -> { if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) { // A SAS tx has been started following this request setState { diff --git a/vector/src/main/res/layout/fragment_progress.xml b/vector/src/main/res/layout/fragment_progress.xml new file mode 100644 index 0000000000..a7a2076209 --- /dev/null +++ b/vector/src/main/res/layout/fragment_progress.xml @@ -0,0 +1,14 @@ + + + + + + From c8a8e0f2da50b5f6808b12adf6d9f9c6f14957a6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Nov 2020 10:54:22 +0100 Subject: [PATCH 167/231] Format source --- .../crypto/verification/VerificationBottomSheet.kt | 14 +++++++------- .../VerificationBottomSheetViewModel.kt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt index f310a6e3a3..a9b76366df 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheet.kt @@ -106,7 +106,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { viewModel.observeViewEvents { when (it) { - is VerificationBottomSheetViewEvents.Dismiss -> dismiss() + is VerificationBottomSheetViewEvents.Dismiss -> dismiss() is VerificationBottomSheetViewEvents.AccessSecretStore -> { secretStartForActivityResult.launch(SharedSecureStorageActivity.newIntent( requireContext(), @@ -115,7 +115,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS )) } - is VerificationBottomSheetViewEvents.ModalError -> { + is VerificationBottomSheetViewEvents.ModalError -> { AlertDialog.Builder(requireContext()) .setTitle(getString(R.string.dialog_title_error)) .setMessage(it.errorMessage) @@ -124,7 +124,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { .show() Unit } - VerificationBottomSheetViewEvents.GoToSettings -> { + VerificationBottomSheetViewEvents.GoToSettings -> { dismiss() (activity as? VectorBaseActivity)?.navigator?.openSettings(requireContext(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY) } @@ -248,7 +248,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) }) } - is VerificationTxState.Verified -> { + is VerificationTxState.Verified -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) }) @@ -264,7 +264,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } when (state.qrTransactionState) { - is VerificationTxState.QrScannedByOther -> { + is VerificationTxState.QrScannedByOther -> { showFragment(VerificationQrScannedByOtherFragment::class, Bundle()) return@withState } @@ -278,13 +278,13 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { }) return@withState } - is VerificationTxState.Verified -> { + is VerificationTxState.Verified -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) }) return@withState } - is VerificationTxState.Cancelled -> { + is VerificationTxState.Cancelled -> { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(false, state.qrTransactionState.cancelCode.value, state.isMe)) }) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 611853d48b..23ed9b6483 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -482,7 +482,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } when (tx) { - is SasVerificationTransaction -> { + is SasVerificationTransaction -> { if (tx.transactionId == (state.pendingRequest.invoke()?.transactionId ?: state.transactionId)) { // A SAS tx has been started following this request setState { From 4d9b9cb959e433ad6b1f2e10c9030408e1f6c797 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 14:23:25 +0100 Subject: [PATCH 168/231] Deprecated event m.room.aliases --- .../android/sdk/api/session/events/model/EventType.kt | 6 ++++++ .../sdk/api/session/room/model/RoomAliasesContent.kt | 3 +++ 2 files changed, 9 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 0a7f3ff09f..68874a1fb1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -49,6 +49,12 @@ object EventType { const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" + + /** + * Note that this Event has been deprecated, see + * - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events + * - https://github.com/matrix-org/matrix-doc/pull/2432 + */ const val STATE_ROOM_ALIASES = "m.room.aliases" const val STATE_ROOM_TOMBSTONE = "m.room.tombstone" const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt index f70e013786..59989f3045 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomAliasesContent.kt @@ -21,6 +21,9 @@ import com.squareup.moshi.JsonClass /** * Class representing the EventType.STATE_ROOM_ALIASES state event content + * Note that this Event has been deprecated, see + * - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events + * - https://github.com/matrix-org/matrix-doc/pull/2432 */ @JsonClass(generateAdapter = true) data class RoomAliasesContent( From 03715e0939bbfee23f82e9274a2a4a5683dcba28 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 14:29:48 +0100 Subject: [PATCH 169/231] Do not use m.room.aliases event to compute a room name (#2428) --- .../session/room/membership/RoomDisplayNameResolver.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index a7dfcfc96f..f744af94c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -71,12 +71,6 @@ internal class RoomDisplayNameResolver @Inject constructor( return name } - val aliases = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root - name = ContentMapper.map(aliases?.content).toModel()?.aliases?.firstOrNull() - if (!name.isNullOrEmpty()) { - return name - } - val roomMembers = RoomMemberHelper(realm, roomId) val activeMembers = roomMembers.queryActiveRoomMembersEvent().findAll() From e2a89c22dab42d213525f3d0b8e4555348e37530 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 14:37:19 +0100 Subject: [PATCH 170/231] Do not show m.room.aliases event in the timeline (#2428) --- .../home/room/detail/timeline/factory/TimelineItemFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 575f28b610..243cbbd0e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -53,7 +53,6 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_AVATAR, EventType.STATE_ROOM_MEMBER, EventType.STATE_ROOM_THIRD_PARTY_INVITE, - EventType.STATE_ROOM_ALIASES, EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_JOIN_RULES, EventType.STATE_ROOM_HISTORY_VISIBILITY, @@ -79,6 +78,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me encryptedItemFactory.create(event, nextEvent, highlight, callback) } } + EventType.STATE_ROOM_ALIASES, EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_KEY, From 7c2fea862374348d997be28af06d6916d1f70d64 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 14:55:16 +0100 Subject: [PATCH 171/231] Typo --- matrix-sdk-android/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index f77cd3203d..c4b579df63 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -246,7 +246,7 @@ %1$s removed %2$s as an address for this room. - %1$s removed %3$s as addresses for this room. + %1$s removed %2$s as addresses for this room. From 0d93105bcd140ed9940ec6beb5db0b31ac504084 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Nov 2020 15:29:35 +0100 Subject: [PATCH 172/231] Rended m.room.canonical_alias event in the timeline, considering alt_aliases (#2428) --- .../room/model/RoomCanonicalAliasContent.kt | 11 ++- .../src/main/res/values/strings.xml | 27 +++++++ .../timeline/format/NoticeEventFormatter.kt | 75 ++++++++++++++++--- 3 files changed, 102 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt index 5487b2ff82..4e8bd2e71b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomCanonicalAliasContent.kt @@ -24,5 +24,14 @@ import com.squareup.moshi.JsonClass */ @JsonClass(generateAdapter = true) data class RoomCanonicalAliasContent( - @Json(name = "alias") val canonicalAlias: String? = null + /** + * The canonical alias for the room. If not present, null, or empty the room should be considered to have no canonical alias. + */ + @Json(name = "alias") val canonicalAlias: String? = null, + + /** + * Alternative aliases the room advertises. + * This list can have aliases despite the alias field being null, empty, or otherwise not present. + */ + @Json(name = "alt_aliases") val alternativeAliases: List? = null ) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index c4b579df63..7a0fe1d735 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -262,6 +262,33 @@ "%1$s removed the main address for this room." "You removed the main address for this room." + + %1$s added the alternative address %2$s for this room. + %1$s added the alternative addresses %2$s for this room. + + + + You added the alternative address %1$s for this room. + You added the alternative addresses %1$s for this room. + + + + %1$s removed the alternative address %2$s for this room. + %1$s removed the alternative addresses %2$s for this room. + + + + You removed the alternative address %1$s for this room. + You removed the alternative addresses %1$s for this room. + + + %1$s changed the alternative addresses for this room. + You changed the alternative addresses for this room. + %1$s changed the main and alternative addresses for this room. + You changed the main and alternative addresses for this room. + %1$s changed the addresses for this room. + You changed the addresses for this room. + "%1$s has allowed guests to join the room." "You have allowed guests to join the room." "%1$s has allowed guests to join here." diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c4cc2e87b0..0db6a374e9 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 @@ -465,21 +465,76 @@ class NoticeEventFormatter @Inject constructor( private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? { val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel() - val canonicalAlias = eventContent?.canonicalAlias - return canonicalAlias - ?.takeIf { it.isNotBlank() } - ?.let { - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_canonical_alias_set_by_you, it) - } else { - sp.getString(R.string.notice_room_canonical_alias_set, senderName, it) - } + val prevContent: RoomCanonicalAliasContent? = event.resolvedPrevContent().toModel() + val canonicalAlias = eventContent?.canonicalAlias?.takeIf { it.isNotEmpty() } + val prevCanonicalAlias = prevContent?.canonicalAlias?.takeIf { it.isNotEmpty() } + val altAliases = eventContent?.alternativeAliases.orEmpty() + val prevAltAliases = prevContent?.alternativeAliases.orEmpty() + val added = altAliases - prevAltAliases + val removed = prevAltAliases - altAliases + + return if (added.isEmpty() && removed.isEmpty() && canonicalAlias == prevCanonicalAlias) { + // in case there is no difference between the two events say something as we can't simply hide the event from here + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_no_change_by_you) + } else { + sp.getString(R.string.notice_room_canonical_alias_no_change, senderName) + } + } else if (added.isEmpty() && removed.isEmpty()) { + // Canonical has changed + if (canonicalAlias != null) { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_set_by_you, canonicalAlias) + } else { + sp.getString(R.string.notice_room_canonical_alias_set, senderName, canonicalAlias) } - ?: if (event.isSentByCurrentUser()) { + } else { + if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_room_canonical_alias_unset_by_you) } else { sp.getString(R.string.notice_room_canonical_alias_unset, senderName) } + } + } else if (added.isEmpty()) { + if (canonicalAlias == prevCanonicalAlias) { + // Some alternative has been removed + if (event.isSentByCurrentUser()) { + sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed_by_you, removed.size, removed.joinToString()) + } else { + sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed, removed.size, senderName, removed.joinToString()) + } + } else { + // Main and removed + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed_by_you) + } else { + sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed, senderName) + } + } + } else if (removed.isEmpty()) { + if (canonicalAlias == prevCanonicalAlias) { + // Some alternative has been added + if (event.isSentByCurrentUser()) { + sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added_by_you, added.size, added.joinToString()) + } else { + sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added, added.size, senderName, added.joinToString()) + } + } else { + // Main and added + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed_by_you) + } else { + sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed, senderName) + } + } + } else { + // Alternative added and removed + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_alternative_changed_by_you) + } else { + sp.getString(R.string.notice_room_canonical_alias_alternative_changed, senderName) + } + } } private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, rs: RoomSummary?): String? { From a6f56ace2441d0079f463ddb8cba81d10dc8142a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 13:05:23 +0100 Subject: [PATCH 173/231] Create a dedicated screen to manage room alias (#2428) - WIP --- .../java/org/matrix/android/sdk/rx/RxRoom.kt | 8 - .../android/sdk/api/session/room/Room.kt | 2 + .../api/session/room/alias/AliasService.kt | 24 +++ .../sdk/internal/session/room/DefaultRoom.kt | 3 + .../sdk/internal/session/room/RoomAPI.kt | 8 + .../sdk/internal/session/room/RoomFactory.kt | 3 + .../sdk/internal/session/room/RoomModule.kt | 5 + .../session/room/alias/DefaultAliasService.kt | 36 ++++ .../session/room/alias/GetAliasesResponse.kt | 28 +++ .../room/alias/GetRoomLocalAliasesTask.kt | 44 +++++ .../im/vector/app/core/di/FragmentModule.kt | 6 + .../roomprofile/RoomProfileActivity.kt | 6 + .../roomprofile/RoomProfileSharedAction.kt | 1 + .../roomprofile/alias/RoomAliasAction.kt | 31 +++ .../roomprofile/alias/RoomAliasController.kt | 120 ++++++++++++ .../roomprofile/alias/RoomAliasFragment.kt | 112 +++++++++++ .../roomprofile/alias/RoomAliasViewEvents.kt | 28 +++ .../roomprofile/alias/RoomAliasViewModel.kt | 182 ++++++++++++++++++ .../roomprofile/alias/RoomAliasViewState.kt | 41 ++++ .../settings/RoomSettingsAction.kt | 1 - .../settings/RoomSettingsController.kt | 25 ++- .../settings/RoomSettingsFragment.kt | 8 +- .../settings/RoomSettingsViewModel.kt | 14 +- .../settings/RoomSettingsViewState.kt | 2 - vector/src/main/res/values/strings.xml | 19 ++ 25 files changed, 718 insertions(+), 39 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.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 86f2d26808..826ada358b 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 @@ -127,14 +127,6 @@ class RxRoom(private val room: Room) { room.updateName(name, it) } - fun addRoomAlias(alias: String): Completable = completableBuilder { - room.addRoomAlias(alias, it) - } - - fun updateCanonicalAlias(alias: String): Completable = completableBuilder { - room.updateCanonicalAlias(alias, it) - } - fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder { room.updateHistoryReadability(readability, it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 837bda031b..cb6690b5c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService import org.matrix.android.sdk.api.session.room.members.MembershipService @@ -46,6 +47,7 @@ interface Room : DraftService, ReadService, TypingService, + AliasService, TagsService, MembershipService, StateService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt new file mode 100644 index 0000000000..c7d5657157 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.alias + +interface AliasService { + /** + * Get list of local alias of the room + */ + suspend fun getRoomAliases(): List +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index c7bb640f7c..7a819250cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -58,6 +59,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val roomCallService: RoomCallService, private val readService: ReadService, private val typingService: TypingService, + private val aliasService: AliasService, private val tagsService: TagsService, private val cryptoService: CryptoService, private val relationService: RelationService, @@ -76,6 +78,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, RoomCallService by roomCallService, ReadService by readService, TypingService by typingService, + AliasService by aliasService, TagsService by tagsService, RelationService by relationService, MembershipService by roomMembersService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index fc80842f73..f0a8dc5177 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtoc import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody +import org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse @@ -332,10 +333,17 @@ internal interface RoomAPI { * Add alias to the room. * @param roomAlias the room alias. */ + // TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md) @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") fun addRoomAlias(@Path("roomAlias") roomAlias: String, @Body body: AddRoomAliasBody): Call + /** + * Get local aliases of this room + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases") + fun getAliases(@Path("roomId") roomId: String): Call + /** * Inform that the user is starting to type or has stopped typing */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index d4fa040d06..63370a1ad8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService @@ -54,6 +55,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val roomCallServiceFactory: DefaultRoomCallService.Factory, private val readServiceFactory: DefaultReadService.Factory, private val typingServiceFactory: DefaultTypingService.Factory, + private val aliasServiceFactory: DefaultAliasService.Factory, private val tagsServiceFactory: DefaultTagsService.Factory, private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, @@ -76,6 +78,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomCallService = roomCallServiceFactory.create(roomId), readService = readServiceFactory.create(roomId), typingService = typingServiceFactory.create(roomId), + aliasService = aliasServiceFactory.create(roomId), tagsService = tagsServiceFactory.create(roomId), cryptoService = cryptoService, relationService = relationServiceFactory.create(roomId), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 6381796ee0..70721264bd 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 @@ -29,7 +29,9 @@ import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomIdByAliasTask +import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAliasesTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask +import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask @@ -181,6 +183,9 @@ internal abstract class RoomModule { @Binds abstract fun bindGetRoomIdByAliasTask(task: DefaultGetRoomIdByAliasTask): GetRoomIdByAliasTask + @Binds + abstract fun bindGetRoomLocalAliasesTask(task: DefaultGetRoomLocalAliasesTask): GetRoomLocalAliasesTask + @Binds abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt new file mode 100644 index 0000000000..129eb82333 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.alias + +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import org.matrix.android.sdk.api.session.room.alias.AliasService + +internal class DefaultAliasService @AssistedInject constructor( + @Assisted private val roomId: String, + private val getRoomLocalAliasesTask: GetRoomLocalAliasesTask +) : AliasService { + + @AssistedInject.Factory + interface Factory { + fun create(roomId: String): AliasService + } + + override suspend fun getRoomAliases(): List { + return getRoomLocalAliasesTask.execute(GetRoomLocalAliasesTask.Params(roomId)) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt new file mode 100644 index 0000000000..499abf33aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.alias + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class GetAliasesResponse( + /** + * The list of aliases currently defined on the local server for the given room + */ + @Json(name = "aliases") val aliases: List = emptyList() +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt new file mode 100644 index 0000000000..7cfce4ecdc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.alias + +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetRoomLocalAliasesTask : Task> { + data class Params( + val roomId: String + ) +} + +internal class DefaultGetRoomLocalAliasesTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus +) : GetRoomLocalAliasesTask { + + override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List { + // We do not check for "org.matrix.msc2432", so the API may be missing + val response = executeRequest(eventBus) { + apiCall = roomAPI.getAliases(roomId = params.roomId) + } + + return response.aliases + } +} diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 32c98922fb..60c14d1579 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 @@ -83,6 +83,7 @@ import im.vector.app.features.roomprofile.RoomProfileFragment import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment +import im.vector.app.features.roomprofile.alias.RoomAliasFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.app.features.roomprofile.uploads.files.RoomUploadsFilesFragment import im.vector.app.features.roomprofile.uploads.media.RoomUploadsMediaFragment @@ -363,6 +364,11 @@ interface FragmentModule { @FragmentKey(RoomSettingsFragment::class) fun bindRoomSettingsFragment(fragment: RoomSettingsFragment): Fragment + @Binds + @IntoMap + @FragmentKey(RoomAliasFragment::class) + fun bindRoomAliasFragment(fragment: RoomAliasFragment): Fragment + @Binds @IntoMap @FragmentKey(RoomMemberProfileFragment::class) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 609042ffa4..2204150a35 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -36,6 +36,7 @@ import im.vector.app.features.room.RequireActiveMembershipViewState import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment +import im.vector.app.features.roomprofile.alias.RoomAliasFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import javax.inject.Inject @@ -100,6 +101,7 @@ class RoomProfileActivity : when (sharedAction) { is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() + is RoomProfileSharedAction.OpenRoomAlias -> openRoomAlias() is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() } @@ -135,6 +137,10 @@ class RoomProfileActivity : addFragmentToBackstack(R.id.simpleFragmentContainer, RoomSettingsFragment::class.java, roomProfileArgs) } + private fun openRoomAlias() { + addFragmentToBackstack(R.id.simpleFragmentContainer, RoomAliasFragment::class.java, roomProfileArgs) + } + private fun openRoomMembers() { addFragmentToBackstack(R.id.simpleFragmentContainer, RoomMemberListFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 0052ddee99..0449f0db15 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -23,6 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction */ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() + object OpenRoomAlias : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt new file mode 100644 index 0000000000..cb5916747a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class RoomAliasAction : VectorViewModelAction { + // Canonical + data class AddAlias(val alias: String) : RoomAliasAction() + data class RemoveAlias(val alias: String) : RoomAliasAction() + data class SetCanonicalAlias(val canonicalAlias: String) : RoomAliasAction() + object UnSetCanonicalAlias : RoomAliasAction() + + // Local + data class AddLocalAlias(val aliasLocalPart: String) : RoomAliasAction() + data class RemoveLocalAlias(val alias: String) : RoomAliasAction() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt new file mode 100644 index 0000000000..b8e1a12688 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias + +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.app.R +import im.vector.app.core.epoxy.errorWithRetryItem +import im.vector.app.core.epoxy.loadingItem +import im.vector.app.core.epoxy.profiles.buildProfileSection +import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.discovery.settingsInfoItem +import im.vector.app.features.settings.threepids.threePidItem +import javax.inject.Inject + +class RoomAliasController @Inject constructor( + private val stringProvider: StringProvider, + private val errorFormatter: ErrorFormatter, + colorProvider: ColorProvider +) : TypedEpoxyController() { + + interface Callback { + fun removeAlias(altAlias: String) + fun setCanonicalAlias(alias: String) + fun unsetCanonicalAlias() + fun addLocalAlias(alias: String) + fun removeLocalAlias(alias: String) + } + + private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) + + var callback: Callback? = null + + init { + setData(null) + } + + override fun buildModels(data: RoomAliasViewState?) { + data ?: return + + buildProfileSection( + stringProvider.getString(R.string.room_alias_published_alias_title) + ) + settingsInfoItem { + id("publishedInfo") + helperTextResId(R.string.room_alias_published_alias_subtitle) + } + + // TODO Canonical + if (data.alternativeAliases.isNotEmpty()) { + settingsInfoItem { + id("otherPublished") + helperTextResId(R.string.room_alias_published_other) + } + data.alternativeAliases.forEachIndexed { idx, altAlias -> + // TODO Rename this item to a more generic name + threePidItem { + id("alt_$idx") + title(altAlias) + deleteClickListener { callback?.removeAlias(altAlias) } + } + } + } + + // Local + buildProfileSection( + stringProvider.getString(R.string.room_alias_local_address_title) + ) + settingsInfoItem { + id("localInfo") + helperText(stringProvider.getString(R.string.room_alias_local_address_subtitle, data.homeServerName)) + } + + buildLocalInfo(data) + } + + private fun buildLocalInfo(data: RoomAliasViewState) { + when (val localAliases = data.localAliases) { + is Uninitialized -> { + loadingItem { + id("loadingAliases") + } + } + is Success -> { + localAliases().forEachIndexed { idx, localAlias -> + // TODO Rename this item to a more generic name + threePidItem { + id("loc_$idx") + title(localAlias) + deleteClickListener { callback?.removeLocalAlias(localAlias) } + } + } + } + is Fail -> { + errorWithRetryItem { + id("alt_error") + text(errorFormatter.toHumanReadable(localAliases.error)) + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt new file mode 100644 index 0000000000..3947937643 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.toast +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.roomprofile.RoomProfileArgs +import kotlinx.android.synthetic.main.fragment_room_setting_generic.* +import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class RoomAliasFragment @Inject constructor( + val viewModelFactory: RoomAliasViewModel.Factory, + private val controller: RoomAliasController, + private val avatarRenderer: AvatarRenderer +) : + VectorBaseFragment(), + RoomAliasController.Callback { + + private val viewModel: RoomAliasViewModel by fragmentViewModel() + private val roomProfileArgs: RoomProfileArgs by args() + + override fun getLayoutResId() = R.layout.fragment_room_setting_generic + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + controller.callback = this + setupToolbar(roomSettingsToolbar) + roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) + waiting_view_status_text.setText(R.string.please_wait) + waiting_view_status_text.isVisible = true + + viewModel.observeViewEvents { + when (it) { + is RoomAliasViewEvents.Failure -> showFailure(it.throwable) + RoomAliasViewEvents.Success -> showSuccess() + }.exhaustive + } + } + + private fun showSuccess() { + activity?.toast(R.string.room_settings_save_success) + } + + override fun onDestroyView() { + controller.callback = null + roomSettingsRecyclerView.cleanup() + super.onDestroyView() + } + + override fun invalidate() = withState(viewModel) { viewState -> + controller.setData(viewState) + renderRoomSummary(viewState) + } + + private fun renderRoomSummary(state: RoomAliasViewState) { + waiting_view.isVisible = state.isLoading + + state.roomSummary()?.let { + roomSettingsToolbarTitleView.text = it.displayName + avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) + } + + invalidateOptionsMenu() + } + + override fun removeAlias(altAlias: String) { + viewModel.handle(RoomAliasAction.RemoveAlias(altAlias)) + } + + override fun setCanonicalAlias(alias: String) { + viewModel.handle(RoomAliasAction.SetCanonicalAlias(alias)) + } + + override fun unsetCanonicalAlias() { + viewModel.handle(RoomAliasAction.UnSetCanonicalAlias) + } + + override fun addLocalAlias(alias: String) { + viewModel.handle(RoomAliasAction.AddLocalAlias(alias)) + } + + override fun removeLocalAlias(alias: String) { + viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias)) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt new file mode 100644 index 0000000000..bbd44741b5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewEvents.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package im.vector.app.features.roomprofile.alias + +import im.vector.app.core.platform.VectorViewEvents + +/** + * Transient events for room settings screen + */ +sealed class RoomAliasViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : RoomAliasViewEvents() + object Success : RoomAliasViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt new file mode 100644 index 0000000000..104b003387 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -0,0 +1,182 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias + +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.extensions.exhaustive +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.powerlevel.PowerLevelsObservableFactory +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.rx.mapOptional +import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.rx.unwrap + +class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: RoomAliasViewState, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomAliasViewState): RoomAliasViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomAliasViewState): RoomAliasViewModel? { + val fragment: RoomAliasFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.viewModelFactory.create(state) + } + } + + private val room = session.getRoom(initialState.roomId)!! + + init { + initHomeServerName() + observeRoomSummary() + observeMowerLevel() + observeRoomCanonicalAlias() + getRoomAlias() + } + + private fun initHomeServerName() { + setState { + copy( + homeServerName = session.myUserId.substringAfter(":") + ) + } + } + + private fun getRoomAlias() { + setState { + copy( + localAliases = Loading() + ) + } + + viewModelScope.launch { + runCatching { room.getRoomAliases() } + .fold( + { + setState { copy(localAliases = Success(it)) } + }, + { + setState { copy(localAliases = Fail(it)) } + } + ) + } + } + + private fun observeRoomSummary() { + room.rx().liveRoomSummary() + .unwrap() + .execute { async -> + copy( + roomSummary = async + ) + } + } + + private fun observeMowerLevel() { + PowerLevelsObservableFactory(room) + .createObservable() + .subscribe { + val powerLevelsHelper = PowerLevelsHelper(it) + val permissions = RoomAliasViewState.ActionPermissions( + canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + EventType.STATE_ROOM_CANONICAL_ALIAS), + ) + setState { copy(actionPermissions = permissions) } + } + .disposeOnClear() + } + + /** + * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. + */ + private fun observeRoomCanonicalAlias() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_CANONICAL_ALIAS, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + setState { + copy( + canonicalAlias = it.canonicalAlias, + alternativeAliases = it.alternativeAliases.orEmpty() + ) + } + } + .disposeOnClear() + } + + override fun handle(action: RoomAliasAction) { + when (action) { + is RoomAliasAction.AddAlias -> handleAddAlias(action) + is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) + is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() + is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action) + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + }.exhaustive + } + + private fun handleAddAlias(action: RoomAliasAction.AddAlias) { + TODO("Not yet implemented") + } + + private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) { + TODO("Not yet implemented") + } + + private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) { + //room.updateCanonicalAlias() + TODO("Not yet implemented") + } + + private fun handleUnsetCanonicalAlias() { + TODO("Not yet implemented") + } + + private fun handleAddLocalAlias(action: RoomAliasAction.AddLocalAlias) { + TODO("Not yet implemented") + } + + private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) { + TODO("Not yet implemented") + } + + private fun postLoading(isLoading: Boolean) { + setState { + copy(isLoading = isLoading) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt new file mode 100644 index 0000000000..be9bde27a6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.roomprofile.RoomProfileArgs +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +data class RoomAliasViewState( + val roomId: String, + val homeServerName: String = "", + val roomSummary: Async = Uninitialized, + val isLoading: Boolean = false, + val canonicalAlias: String? = null, + val alternativeAliases: List = emptyList(), + val localAliases: Async> = Uninitialized, + val actionPermissions: ActionPermissions = ActionPermissions() +) : MvRxState { + + constructor(args: RoomProfileArgs) : this(roomId = args.roomId) + + data class ActionPermissions( + val canChangeCanonicalAlias: Boolean = false + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index f0a7b38478..f88a7cbfd5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -24,7 +24,6 @@ sealed class RoomSettingsAction : VectorViewModelAction { data class SetRoomName(val newName: String) : RoomSettingsAction() data class SetRoomTopic(val newTopic: String) : RoomSettingsAction() data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() - data class SetRoomCanonicalAlias(val newCanonicalAlias: String) : RoomSettingsAction() object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 3c73e6ed46..6c944ef2f8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -46,7 +46,7 @@ class RoomSettingsController @Inject constructor( fun onNameChanged(name: String) fun onTopicChanged(topic: String) fun onHistoryVisibilityClicked() - fun onAliasChanged(alias: String) + fun onOpenAlias() } private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -67,13 +67,13 @@ class RoomSettingsController @Inject constructor( id("avatar") enabled(data.actionPermissions.canChangeAvatar) when (val avatarAction = data.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { // Use the current value avatarRenderer(avatarRenderer) // We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. matrixItem(roomSummary.toMatrixItem().copy(avatarUrl = data.currentRoomAvatarUrl)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> + RoomSettingsViewState.AvatarAction.DeleteAvatar -> imageUri(null) is RoomSettingsViewState.AvatarAction.UpdateAvatar -> imageUri(avatarAction.newAvatarUri) @@ -108,16 +108,15 @@ class RoomSettingsController @Inject constructor( } } - formEditTextItem { - id("alias") - enabled(data.actionPermissions.canChangeCanonicalAlias) - value(data.newCanonicalAlias ?: roomSummary.canonicalAlias) - hint(stringProvider.getString(R.string.room_settings_addresses_add_new_address)) - - onTextChange { text -> - callback?.onAliasChanged(text) - } - } + buildProfileAction( + id = "alias", + title = stringProvider.getString(R.string.room_settings_alias_title), + subtitle = stringProvider.getString(R.string.room_settings_alias_subtitle), + dividerColor = dividerColor, + divider = true, + editable = true, + action = { callback?.onOpenAlias() } + ) buildProfileAction( id = "historyReadability", diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 6637b7d943..01ee1cf9b8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -39,6 +39,8 @@ import im.vector.app.core.utils.toast import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.app.features.roomprofile.RoomProfileSharedAction +import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.matrix.android.sdk.api.session.events.model.toModel @@ -61,6 +63,7 @@ class RoomSettingsFragment @Inject constructor( GalleryOrCameraDialogHelper.Listener { private val viewModel: RoomSettingsViewModel by fragmentViewModel() + private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel private val roomProfileArgs: RoomProfileArgs by args() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) @@ -70,6 +73,7 @@ class RoomSettingsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) controller.callback = this setupToolbar(roomSettingsToolbar) roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) @@ -164,8 +168,8 @@ class RoomSettingsFragment @Inject constructor( return@withState } - override fun onAliasChanged(alias: String) { - viewModel.handle(RoomSettingsAction.SetRoomCanonicalAlias(alias)) + override fun onOpenAlias() { + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAlias) } override fun onImageReady(uri: Uri?) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 05a75a585b..7e76f85d44 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -68,12 +68,10 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: selectSubscribe( RoomSettingsViewState::avatarAction, RoomSettingsViewState::newName, - RoomSettingsViewState::newCanonicalAlias, RoomSettingsViewState::newTopic, RoomSettingsViewState::newHistoryVisibility, RoomSettingsViewState::roomSummary) { avatarAction, newName, - newCanonicalAlias, newTopic, newHistoryVisibility, asyncSummary -> @@ -83,7 +81,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: showSaveAction = avatarAction !is RoomSettingsViewState.AvatarAction.None || summary?.name != newName || summary?.topic != newTopic - || summary?.canonicalAlias != newCanonicalAlias?.takeIf { it.isNotEmpty() } || newHistoryVisibility != null ) } @@ -99,8 +96,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: historyVisibilityEvent = room.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY), roomSummary = async, newName = roomSummary?.name, - newTopic = roomSummary?.topic, - newCanonicalAlias = roomSummary?.canonicalAlias + newTopic = roomSummary?.topic ) } @@ -113,8 +109,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), - canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_CANONICAL_ALIAS), canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_HISTORY_VISIBILITY) ) @@ -143,7 +137,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) } is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) } is RoomSettingsAction.SetRoomHistoryVisibility -> setState { copy(newHistoryVisibility = action.visibility) } - is RoomSettingsAction.SetRoomCanonicalAlias -> setState { copy(newCanonicalAlias = action.newCanonicalAlias) } is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() }.exhaustive @@ -191,11 +184,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: operationList.add(room.rx().updateTopic(state.newTopic ?: "")) } - if (state.newCanonicalAlias != null && summary?.canonicalAlias != state.newCanonicalAlias.takeIf { it.isNotEmpty() }) { - operationList.add(room.rx().addRoomAlias(state.newCanonicalAlias)) - operationList.add(room.rx().updateCanonicalAlias(state.newCanonicalAlias)) - } - if (state.newHistoryVisibility != null) { operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility)) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 2cadc8f798..bdcd9e6bc7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -35,7 +35,6 @@ data class RoomSettingsViewState( val newName: String? = null, val newTopic: String? = null, val newHistoryVisibility: RoomHistoryVisibility? = null, - val newCanonicalAlias: String? = null, val showSaveAction: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions() ) : MvRxState { @@ -46,7 +45,6 @@ data class RoomSettingsViewState( val canChangeAvatar: Boolean = false, val canChangeName: Boolean = false, val canChangeTopic: Boolean = false, - val canChangeCanonicalAlias: Boolean = false, val canChangeHistoryReadability: Boolean = false ) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 2cb6eb9af9..59bd17ee70 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1022,6 +1022,25 @@ Who can read history? Who can access this room? + + Room addresses + See and managed addresses of this room + + Room Addresses + Published Addresses + Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first. + Main address + Other published addresses: + + Publish this room to the public in %1$s\'s room directory? + No other published addresses yet, add one below + New published address (e.g. #alias:server) + + Local Addresses + + Set addresses for this room so users can find this room through your homeserver (%1$s) + This room has no local addresses + Anyone Members only (since the point in time of selecting this option) From 5b618ba1f3283e988621ad6892fb6eb30ffe8065 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 14:30:48 +0100 Subject: [PATCH 174/231] Create RoomDirectoryAPI, and handle deletion of alias --- .../sdk/api/session/room/RoomService.kt | 5 ++ .../session/directory/DirectoryAPI.kt | 53 +++++++++++++++++++ .../session/room/DefaultRoomService.kt | 6 +++ .../sdk/internal/session/room/RoomAPI.kt | 19 ------- .../sdk/internal/session/room/RoomModule.kt | 13 +++++ .../session/room/alias/AddRoomAliasTask.kt | 10 ++-- .../session/room/alias/DeleteRoomAliasTask.kt | 43 +++++++++++++++ .../room/alias/GetRoomIdByAliasTask.kt | 6 +-- .../session/room/create/CreateRoomTask.kt | 4 +- .../roomprofile/alias/RoomAliasFragment.kt | 25 +++++++-- .../roomprofile/alias/RoomAliasViewModel.kt | 21 +++++--- vector/src/main/res/values/strings.xml | 1 + 12 files changed, 169 insertions(+), 37 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index f30037e5c2..477bef66cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -122,6 +122,11 @@ interface RoomService { searchOnServer: Boolean, callback: MatrixCallback>): Cancelable + /** + * Delete a room alias + */ + suspend fun deleteRoomAlias(roomAlias: String) + /** * Return a live data of all local changes membership that happened since the session has been opened. * It allows you to track this in your client to known what is currently being processed by the SDK. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt new file mode 100644 index 0000000000..122d5fef92 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.directory + +import org.matrix.android.sdk.internal.network.NetworkConstants +import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path + +internal interface DirectoryAPI { + /** + * Get the room ID associated to the room alias. + * + * @param roomAlias the room alias. + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") + fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call + + /** + * Add alias to the room. + * @param roomAlias the room alias. + */ + // TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md) + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") + fun addRoomAlias(@Path("roomAlias") roomAlias: String, + @Body body: AddRoomAliasBody): Call + + /** + * Delete a room aliases + * @param roomAlias the room alias. + */ + @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") + fun deleteRoomAlias(@Path("roomAlias") roomAlias: String): Call +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 28656463c1..9ec985e0b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource @@ -53,6 +54,7 @@ internal class DefaultRoomService @Inject constructor( private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, private val roomIdByAliasTask: GetRoomIdByAliasTask, + private val deleteRoomAliasTask: DeleteRoomAliasTask, private val roomGetter: RoomGetter, private val roomSummaryDataSource: RoomSummaryDataSource, private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource, @@ -125,6 +127,10 @@ internal class DefaultRoomService @Inject constructor( .executeBy(taskExecutor) } + override suspend fun deleteRoomAlias(roomAlias: String) { + deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) + } + override fun getChangeMembershipsLive(): LiveData> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index f0a8dc5177..44f52f5f3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -23,9 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsRe import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants -import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody import org.matrix.android.sdk.internal.session.room.alias.GetAliasesResponse -import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.create.CreateRoomBody import org.matrix.android.sdk.internal.session.room.create.CreateRoomResponse import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse @@ -321,23 +319,6 @@ internal interface RoomAPI { @Path("eventId") eventId: String, @Body body: ReportContentBody): Call - /** - * Get the room ID associated to the room alias. - * - * @param roomAlias the room alias. - */ - @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call - - /** - * Add alias to the room. - * @param roomAlias the room alias. - */ - // TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md) - @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun addRoomAlias(@Path("roomAlias") roomAlias: String, - @Body body: AddRoomAliasBody): Call - /** * Get local aliases of this room */ 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 70721264bd..8ad82e0e15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -26,10 +26,13 @@ import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.internal.session.DefaultFileService import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultAddRoomAliasTask +import org.matrix.android.sdk.internal.session.room.alias.DefaultDeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAliasesTask +import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask @@ -92,6 +95,13 @@ internal abstract class RoomModule { return retrofit.create(RoomAPI::class.java) } + @Provides + @JvmStatic + @SessionScope + fun providesDirectoryAPI(retrofit: Retrofit): DirectoryAPI { + return retrofit.create(DirectoryAPI::class.java) + } + @Provides @JvmStatic fun providesParser(): Parser { @@ -189,6 +199,9 @@ internal abstract class RoomModule { @Binds abstract fun bindAddRoomAliasTask(task: DefaultAddRoomAliasTask): AddRoomAliasTask + @Binds + abstract fun bindDeleteRoomAliasTask(task: DefaultDeleteRoomAliasTask): DeleteRoomAliasTask + @Binds abstract fun bindSendTypingTask(task: DefaultSendTypingTask): SendTypingTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt index 695be3f633..d3ef16bc3d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.session.room.alias -import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.task.Task import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject internal interface AddRoomAliasTask : Task { @@ -30,13 +30,13 @@ internal interface AddRoomAliasTask : Task { } internal class DefaultAddRoomAliasTask @Inject constructor( - private val roomAPI: RoomAPI, + private val directoryAPI: DirectoryAPI, private val eventBus: EventBus ) : AddRoomAliasTask { override suspend fun execute(params: AddRoomAliasTask.Params) { executeRequest(eventBus) { - apiCall = roomAPI.addRoomAlias( + apiCall = directoryAPI.addRoomAlias( roomAlias = params.roomAlias, body = AddRoomAliasBody( roomId = params.roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt new file mode 100644 index 0000000000..3400fd994c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.alias + +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface DeleteRoomAliasTask : Task { + data class Params( + val roomAlias: String + ) +} + +internal class DefaultDeleteRoomAliasTask @Inject constructor( + private val directoryAPI: DirectoryAPI, + private val eventBus: EventBus +) : DeleteRoomAliasTask { + + override suspend fun execute(params: DeleteRoomAliasTask.Params) { + executeRequest(eventBus) { + apiCall = directoryAPI.deleteRoomAlias( + roomAlias = params.roomAlias + ) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index 58a119cc77..3c47ee6ef0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -38,7 +38,7 @@ internal interface GetRoomIdByAliasTask : Task(eventBus) { - apiCall = roomAPI.getRoomIdByAlias(params.roomAlias) + apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias) } }?.roomId Optional.from(roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 0fe9b0ba68..309b8dbfaa 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 @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask @@ -47,6 +48,7 @@ internal interface CreateRoomTask : Task internal class DefaultCreateRoomTask @Inject constructor( private val roomAPI: RoomAPI, + private val directoryAPI: DirectoryAPI, @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, private val directChatsHelper: DirectChatsHelper, @@ -72,7 +74,7 @@ internal class DefaultCreateRoomTask @Inject constructor( val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":") try { executeRequest(eventBus) { - apiCall = roomAPI.getRoomIdByAlias(fullAlias) + apiCall = directoryAPI.getRoomIdByAlias(fullAlias) } } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == 404) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 3947937643..fe3ca048a8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -16,13 +16,16 @@ package im.vector.app.features.roomprofile.alias +import android.content.DialogInterface import android.os.Bundle import android.view.View +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.app.R +import im.vector.app.core.dialogs.withColoredButton import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.exhaustive @@ -59,7 +62,7 @@ class RoomAliasFragment @Inject constructor( viewModel.observeViewEvents { when (it) { is RoomAliasViewEvents.Failure -> showFailure(it.throwable) - RoomAliasViewEvents.Success -> showSuccess() + RoomAliasViewEvents.Success -> showSuccess() }.exhaustive } } @@ -91,7 +94,15 @@ class RoomAliasFragment @Inject constructor( } override fun removeAlias(altAlias: String) { - viewModel.handle(RoomAliasAction.RemoveAlias(altAlias)) + AlertDialog.Builder(requireContext()) + .setTitle(R.string.dialog_title_confirmation) + .setMessage(getString(R.string.room_alias_delete_confirmation, altAlias)) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.delete) { _, _ -> + viewModel.handle(RoomAliasAction.RemoveAlias(altAlias)) + } + .show() + .withColoredButton(DialogInterface.BUTTON_POSITIVE) } override fun setCanonicalAlias(alias: String) { @@ -107,6 +118,14 @@ class RoomAliasFragment @Inject constructor( } override fun removeLocalAlias(alias: String) { - viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias)) + AlertDialog.Builder(requireContext()) + .setTitle(R.string.dialog_title_confirmation) + .setMessage(getString(R.string.room_alias_delete_confirmation, alias)) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.delete) { _, _ -> + viewModel.handle(RoomAliasAction.RemoveLocalAlias(alias)) + } + .show() + .withColoredButton(DialogInterface.BUTTON_POSITIVE) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 104b003387..ff3565b4bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -140,12 +140,12 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { - is RoomAliasAction.AddAlias -> handleAddAlias(action) - is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) + is RoomAliasAction.AddAlias -> handleAddAlias(action) + is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) - RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() - is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action) - is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() + is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action) + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) }.exhaustive } @@ -154,7 +154,16 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) { - TODO("Not yet implemented") + setState { + copy(isLoading = true) + } + viewModelScope.launch { + runCatching { session.deleteRoomAlias(action.alias) } + .onFailure { _viewEvents.post(RoomAliasViewEvents.Failure(it)) } + setState { + copy(isLoading = false) + } + } } private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 59bd17ee70..a52d98a4b4 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1031,6 +1031,7 @@ Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first. Main address Other published addresses: + Delete the address \"%1$s\"? Publish this room to the public in %1$s\'s room directory? No other published addresses yet, add one below From 27fc5f265f7458046749c1d3e8c006a6fd68219a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 16:50:00 +0100 Subject: [PATCH 175/231] Add/Remove local alias (#2428) --- .../api/session/room/alias/AliasService.kt | 5 ++ .../api/session/room/alias/RoomAliasError.kt | 23 ++++++ .../session/room/failure/CreateRoomFailure.kt | 7 +- .../api/session/room/state/StateService.kt | 5 -- .../session/directory/DirectoryAPI.kt | 1 - .../session/room/alias/AddRoomAliasTask.kt | 10 ++- .../session/room/alias/DefaultAliasService.kt | 7 +- .../alias/RoomAliasAvailabilityChecker.kt | 61 ++++++++++++++ .../session/room/create/CreateRoomTask.kt | 33 ++------ .../session/room/state/DefaultStateService.kt | 8 -- .../createroom/CreateRoomController.kt | 17 ++-- .../createroom/CreateRoomFragment.kt | 2 +- .../createroom/RoomAliasErrorFormatter.kt | 36 ++++++++ .../roomprofile/alias/RoomAliasAction.kt | 3 +- .../roomprofile/alias/RoomAliasController.kt | 38 +++++++-- .../roomprofile/alias/RoomAliasFragment.kt | 15 +++- .../roomprofile/alias/RoomAliasViewModel.kt | 82 ++++++++++++++----- .../roomprofile/alias/RoomAliasViewState.kt | 2 + vector/src/main/res/values/strings.xml | 1 + 19 files changed, 262 insertions(+), 94 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt index c7d5657157..3060824f0f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt @@ -21,4 +21,9 @@ interface AliasService { * Get list of local alias of the room */ suspend fun getRoomAliases(): List + + /** + * Add local alias to the room + */ + suspend fun addAlias(aliasLocalPart: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt new file mode 100644 index 0000000000..1dbdd9cced --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt @@ -0,0 +1,23 @@ +/* + * 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.api.session.room.alias + +sealed class RoomAliasError : Throwable() { + object AliasEmpty : RoomAliasError() + object AliasNotAvailable : RoomAliasError() + object AliasInvalid : RoomAliasError() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt index b4e2dc645c..208cdd4556 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt @@ -18,13 +18,10 @@ package org.matrix.android.sdk.api.session.room.failure import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError sealed class CreateRoomFailure : Failure.FeatureFailure() { object CreatedWithTimeout : CreateRoomFailure() data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure() - sealed class RoomAliasError : CreateRoomFailure() { - object AliasEmpty : RoomAliasError() - object AliasNotAvailable : RoomAliasError() - object AliasInvalid : RoomAliasError() - } + data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index e4baa58c30..0b25138f57 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -38,11 +38,6 @@ interface StateService { */ fun updateName(name: String, callback: MatrixCallback): Cancelable - /** - * Add new alias to the room. - */ - fun addRoomAlias(roomAlias: String, callback: MatrixCallback): Cancelable - /** * Update the canonical alias of the room */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 122d5fef92..3eff4b05aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -39,7 +39,6 @@ internal interface DirectoryAPI { * Add alias to the room. * @param roomAlias the room alias. */ - // TODO Remove (https://github.com/matrix-org/matrix-doc/blob/rav/proposal/alt_canonical_aliases/proposals/2432-revised-alias-publishing.md) @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") fun addRoomAlias(@Path("roomAlias") roomAlias: String, @Body body: AddRoomAliasBody): Call diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt index d3ef16bc3d..4dad476f03 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt @@ -17,27 +17,33 @@ package org.matrix.android.sdk.internal.session.room.alias import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker.Companion.toFullAlias import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject internal interface AddRoomAliasTask : Task { data class Params( val roomId: String, - val roomAlias: String + val aliasLocalPart: String ) } internal class DefaultAddRoomAliasTask @Inject constructor( + @UserId private val userId: String, private val directoryAPI: DirectoryAPI, + private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker, private val eventBus: EventBus ) : AddRoomAliasTask { override suspend fun execute(params: AddRoomAliasTask.Params) { + aliasAvailabilityChecker.check(params.aliasLocalPart) + executeRequest(eventBus) { apiCall = directoryAPI.addRoomAlias( - roomAlias = params.roomAlias, + roomAlias = params.aliasLocalPart.toFullAlias(userId), body = AddRoomAliasBody( roomId = params.roomId ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt index 129eb82333..b6c69224e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DefaultAliasService.kt @@ -22,7 +22,8 @@ import org.matrix.android.sdk.api.session.room.alias.AliasService internal class DefaultAliasService @AssistedInject constructor( @Assisted private val roomId: String, - private val getRoomLocalAliasesTask: GetRoomLocalAliasesTask + private val getRoomLocalAliasesTask: GetRoomLocalAliasesTask, + private val addRoomAliasTask: AddRoomAliasTask ) : AliasService { @AssistedInject.Factory @@ -33,4 +34,8 @@ internal class DefaultAliasService @AssistedInject constructor( override suspend fun getRoomAliases(): List { return getRoomLocalAliasesTask.execute(GetRoomLocalAliasesTask.Params(roomId)) } + + override suspend fun addAlias(aliasLocalPart: String) { + addRoomAliasTask.execute(AddRoomAliasTask.Params(roomId, aliasLocalPart)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt new file mode 100644 index 0000000000..0abb158521 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.alias + +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import javax.inject.Inject + +internal class RoomAliasAvailabilityChecker @Inject constructor( + @UserId private val userId: String, + private val directoryAPI: DirectoryAPI, + private val eventBus: EventBus +) { + @Throws(RoomAliasError::class) + suspend fun check(aliasLocalPart: String?) { + if (aliasLocalPart.isNullOrEmpty()) { + throw RoomAliasError.AliasEmpty + } + // Check alias availability + val fullAlias = aliasLocalPart.toFullAlias(userId) + try { + executeRequest(eventBus) { + apiCall = directoryAPI.getRoomIdByAlias(fullAlias) + } + } catch (throwable: Throwable) { + if (throwable is Failure.ServerError && throwable.httpCode == 404) { + // This is a 404, so the alias is available: nominal case + null + } else { + // Other error, propagate it + throw throwable + } + } + ?.let { + // Alias already exists: error case + throw RoomAliasError.AliasNotAvailable + } + } + + companion object { + internal fun String.toFullAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 309b8dbfaa..ef792ab98e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.TimeoutCancellationException import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset @@ -31,11 +32,9 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.directory.DirectoryAPI import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask @@ -48,9 +47,8 @@ internal interface CreateRoomTask : Task internal class DefaultCreateRoomTask @Inject constructor( private val roomAPI: RoomAPI, - private val directoryAPI: DirectoryAPI, - @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, + private val aliasAvailabilityChecker: RoomAliasAvailabilityChecker, private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val readMarkersTask: SetReadMarkersTask, @@ -67,28 +65,11 @@ internal class DefaultCreateRoomTask @Inject constructor( } else null if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { - if (params.roomAliasName.isNullOrEmpty()) { - throw CreateRoomFailure.RoomAliasError.AliasEmpty - } - // Check alias availability - val fullAlias = "#" + params.roomAliasName + ":" + userId.substringAfter(":") try { - executeRequest(eventBus) { - apiCall = directoryAPI.getRoomIdByAlias(fullAlias) - } - } catch (throwable: Throwable) { - if (throwable is Failure.ServerError && throwable.httpCode == 404) { - // This is a 404, so the alias is available: nominal case - null - } else { - // Other error, propagate it - throw throwable - } + aliasAvailabilityChecker.check(params.roomAliasName) + } catch (aliasError: RoomAliasError) { + throw CreateRoomFailure.AliasError(aliasError) } - ?.let { - // Alias already exists: error case - throw CreateRoomFailure.RoomAliasError.AliasNotAvailable - } } val createRoomBody = createRoomBodyBuilder.build(params) @@ -106,7 +87,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } else if (throwable.httpCode == 400 && throwable.error.code == MatrixError.M_UNKNOWN && throwable.error.message == "Invalid characters in room alias") { - throw CreateRoomFailure.RoomAliasError.AliasInvalid + throw CreateRoomFailure.AliasError(RoomAliasError.AliasInvalid) } } throw throwable diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 3463b26c8a..1f5b174299 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 @@ -104,14 +104,6 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private ) } - override fun addRoomAlias(roomAlias: String, callback: MatrixCallback): Cancelable { - return addRoomAliasTask - .configureWith(AddRoomAliasTask.Params(roomId, roomAlias)) { - this.callback = callback - } - .executeBy(taskExecutor) - } - override fun updateCanonicalAlias(alias: String, callback: MatrixCallback): Cancelable { return sendStateEvent( eventType = EventType.STATE_ROOM_CANONICAL_ALIAS, diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt index aaf7b6ead5..de9ecb2825 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt @@ -20,7 +20,6 @@ import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading import im.vector.app.R -import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.discovery.settingsSectionTitleItem import im.vector.app.features.form.formAdvancedToggleItem @@ -28,11 +27,13 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.form.formSubmitButtonItem import im.vector.app.features.form.formSwitchItem +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import javax.inject.Inject -class CreateRoomController @Inject constructor(private val stringProvider: StringProvider, - private val errorFormatter: ErrorFormatter +class CreateRoomController @Inject constructor( + private val stringProvider: StringProvider, + private val roomAliasErrorFormatter: RoomAliasErrorFormatter ) : TypedEpoxyController() { var listener: Listener? = null @@ -103,15 +104,7 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin enabled(enableFormElement) value(viewState.roomType.aliasLocalPart) homeServer(":" + viewState.homeServerName) - errorMessage( - when ((viewState.asyncCreateRoomRequest as? Fail)?.error) { - is CreateRoomFailure.RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty - is CreateRoomFailure.RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use - is CreateRoomFailure.RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid - else -> null - } - ?.let { stringProvider.getString(it) } - ) + errorMessage(roomAliasErrorFormatter.format((((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError)) onTextChange { value -> listener?.setAliasLocalPart(value) } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt index fb90752764..204a99929b 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -84,7 +84,7 @@ class CreateRoomFragment @Inject constructor( override fun showFailure(throwable: Throwable) { // Note: RoomAliasError are displayed directly in the form - if (throwable !is CreateRoomFailure.RoomAliasError) { + if (throwable !is CreateRoomFailure.AliasError) { super.showFailure(throwable) } } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt new file mode 100644 index 0000000000..7a23a79ab3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/RoomAliasErrorFormatter.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomdirectory.createroom + +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError +import javax.inject.Inject + +class RoomAliasErrorFormatter @Inject constructor( + private val stringProvider: StringProvider +) { + fun format(roomAliasError: RoomAliasError?): String? { + return when (roomAliasError) { + is RoomAliasError.AliasEmpty -> R.string.create_room_alias_empty + is RoomAliasError.AliasNotAvailable -> R.string.create_room_alias_already_in_use + is RoomAliasError.AliasInvalid -> R.string.create_room_alias_invalid + else -> null + } + ?.let { stringProvider.getString(it) } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt index cb5916747a..a65065f167 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -26,6 +26,7 @@ sealed class RoomAliasAction : VectorViewModelAction { object UnSetCanonicalAlias : RoomAliasAction() // Local - data class AddLocalAlias(val aliasLocalPart: String) : RoomAliasAction() data class RemoveLocalAlias(val alias: String) : RoomAliasAction() + data class SetNewLocalAliasLocalPart(val aliasLocalPart: String) : RoomAliasAction() + object AddLocalAlias : RoomAliasAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index b8e1a12688..cf133da7b7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -25,28 +25,30 @@ import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.discovery.settingsInfoItem +import im.vector.app.features.form.formSubmitButtonItem +import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter +import im.vector.app.features.roomdirectory.createroom.roomAliasEditItem import im.vector.app.features.settings.threepids.threePidItem +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import javax.inject.Inject class RoomAliasController @Inject constructor( private val stringProvider: StringProvider, private val errorFormatter: ErrorFormatter, - colorProvider: ColorProvider + private val roomAliasErrorFormatter: RoomAliasErrorFormatter ) : TypedEpoxyController() { interface Callback { fun removeAlias(altAlias: String) fun setCanonicalAlias(alias: String) fun unsetCanonicalAlias() - fun addLocalAlias(alias: String) fun removeLocalAlias(alias: String) + fun setNewLocalAliasLocalPart(value: String) + fun addLocalAlias() } - private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) - var callback: Callback? = null init { @@ -81,6 +83,10 @@ class RoomAliasController @Inject constructor( } // Local + buildLocalInfo(data) + } + + private fun buildLocalInfo(data: RoomAliasViewState) { buildProfileSection( stringProvider.getString(R.string.room_alias_local_address_title) ) @@ -89,10 +95,6 @@ class RoomAliasController @Inject constructor( helperText(stringProvider.getString(R.string.room_alias_local_address_subtitle, data.homeServerName)) } - buildLocalInfo(data) - } - - private fun buildLocalInfo(data: RoomAliasViewState) { when (val localAliases = data.localAliases) { is Uninitialized -> { loadingItem { @@ -116,5 +118,23 @@ class RoomAliasController @Inject constructor( } } } + + // Add local + roomAliasEditItem { + id("newLocalAlias") + value(data.newLocalAlias) + homeServer(":" + data.homeServerName) + showBottomSeparator(false) + errorMessage(roomAliasErrorFormatter.format((data.asyncNewLocalAliasRequest as? Fail)?.error as? RoomAliasError)) + onTextChange { value -> + callback?.setNewLocalAliasLocalPart(value) + } + } + + formSubmitButtonItem { + id("submit") + buttonTitleId(R.string.action_add) + buttonClickListener { callback?.addLocalAlias() } + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index fe3ca048a8..2f7526ebff 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -35,6 +35,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomprofile.RoomProfileArgs import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -67,6 +68,12 @@ class RoomAliasFragment @Inject constructor( } } + override fun showFailure(throwable: Throwable) { + if (throwable !is RoomAliasError) { + super.showFailure(throwable) + } + } + private fun showSuccess() { activity?.toast(R.string.room_settings_save_success) } @@ -113,8 +120,12 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.UnSetCanonicalAlias) } - override fun addLocalAlias(alias: String) { - viewModel.handle(RoomAliasAction.AddLocalAlias(alias)) + override fun setNewLocalAliasLocalPart(value: String) { + viewModel.handle(RoomAliasAction.SetNewLocalAliasLocalPart(value)) + } + + override fun addLocalAlias() { + viewModel.handle(RoomAliasAction.AddLocalAlias) } override fun removeLocalAlias(alias: String) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index ff3565b4bb..319947c18e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -22,6 +22,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 @@ -62,9 +63,9 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo init { initHomeServerName() observeRoomSummary() - observeMowerLevel() + observePowerLevel() observeRoomCanonicalAlias() - getRoomAlias() + fetchRoomAlias() } private fun initHomeServerName() { @@ -75,7 +76,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } } - private fun getRoomAlias() { + private fun fetchRoomAlias() { setState { copy( localAliases = Loading() @@ -105,7 +106,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } } - private fun observeMowerLevel() { + private fun observePowerLevel() { PowerLevelsObservableFactory(room) .createObservable() .subscribe { @@ -140,29 +141,35 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { - is RoomAliasAction.AddAlias -> handleAddAlias(action) - is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) - is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) - RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() - is RoomAliasAction.AddLocalAlias -> handleAddLocalAlias(action) - is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + is RoomAliasAction.AddAlias -> handleAddAlias() + is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) + is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() + is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) + RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) }.exhaustive } - private fun handleAddAlias(action: RoomAliasAction.AddAlias) { - TODO("Not yet implemented") + private fun handleSetNewLocalAliasLocalPart(action: RoomAliasAction.SetNewLocalAliasLocalPart) { + setState { + copy( + newLocalAlias = action.aliasLocalPart, + asyncNewLocalAliasRequest = Uninitialized + ) + } + } + + private fun handleAddAlias() { + TODO() } private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) { - setState { - copy(isLoading = true) - } + postLoading(true) viewModelScope.launch { runCatching { session.deleteRoomAlias(action.alias) } .onFailure { _viewEvents.post(RoomAliasViewEvents.Failure(it)) } - setState { - copy(isLoading = false) - } + postLoading(false) } } @@ -172,15 +179,48 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun handleUnsetCanonicalAlias() { + // room.updateCanonicalAlias() TODO("Not yet implemented") } - private fun handleAddLocalAlias(action: RoomAliasAction.AddLocalAlias) { - TODO("Not yet implemented") + private fun handleAddLocalAlias() = withState { state -> + setState { + copy( + isLoading = true, + asyncNewLocalAliasRequest = Loading() + ) + } + viewModelScope.launch { + runCatching { room.addAlias(state.newLocalAlias) } + .onFailure { + setState { + copy( + isLoading = false, + asyncNewLocalAliasRequest = Fail(it) + ) + } + _viewEvents.post(RoomAliasViewEvents.Failure(it)) + } + .onSuccess { + setState { + copy( + isLoading = false, + asyncNewLocalAliasRequest = Uninitialized + ) + } + fetchRoomAlias() + } + } } private fun handleRemoveLocalAlias(action: RoomAliasAction.RemoveLocalAlias) { - TODO("Not yet implemented") + postLoading(true) + viewModelScope.launch { + runCatching { session.deleteRoomAlias(action.alias) } + .onFailure { _viewEvents.post(RoomAliasViewEvents.Failure(it)) } + .onSuccess { fetchRoomAlias() } + postLoading(false) + } } private fun postLoading(isLoading: Boolean) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index be9bde27a6..e2021245d8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -30,6 +30,8 @@ data class RoomAliasViewState( val canonicalAlias: String? = null, val alternativeAliases: List = emptyList(), val localAliases: Async> = Uninitialized, + val newLocalAlias: String = "", + val asyncNewLocalAliasRequest: Async = Uninitialized, val actionPermissions: ActionPermissions = ActionPermissions() ) : MvRxState { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a52d98a4b4..c97785e74c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -137,6 +137,7 @@ Open Close Copy + Add Copied to clipboard Disable From e1abd5a05112b41e4933694aeca10f6aaff5f17c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 16:59:32 +0100 Subject: [PATCH 176/231] Fix layout issue --- vector/src/main/res/layout/item_settings_three_pid.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_settings_three_pid.xml b/vector/src/main/res/layout/item_settings_three_pid.xml index a175788d86..0040840ce9 100644 --- a/vector/src/main/res/layout/item_settings_three_pid.xml +++ b/vector/src/main/res/layout/item_settings_three_pid.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?riotx_background" android:minHeight="64dp" android:paddingStart="@dimen/layout_horizontal_margin" android:paddingEnd="@dimen/layout_horizontal_margin"> @@ -12,19 +13,20 @@ android:id="@+id/item_settings_three_pid_icon" android:layout_width="16dp" android:layout_height="16dp" + android:layout_marginEnd="8dp" android:scaleType="center" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/item_settings_three_pid_title" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:src="@drawable/ic_phone" app:tint="?riotx_text_secondary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_phone" /> Date: Mon, 23 Nov 2020 17:17:37 +0100 Subject: [PATCH 177/231] Prepare to update canonical alias state --- .../api/session/room/state/StateService.kt | 4 +- .../session/room/state/DefaultStateService.kt | 9 ++- .../roomprofile/alias/RoomAliasViewModel.kt | 62 ++++++++++++------- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 0b25138f57..1d048f2459 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -40,8 +40,10 @@ interface StateService { /** * Update the canonical alias of the room + * @param alias the canonical alias, or null to reset the canonical alias of this room + * @param altAliases the alternative aliases for this room. It should include the canonical alias if any. */ - fun updateCanonicalAlias(alias: String, callback: MatrixCallback): Cancelable + fun updateCanonicalAlias(alias: String?, altAliases: List, callback: MatrixCallback): Cancelable /** * Update the history readability of the room 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 1f5b174299..8fa0857837 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -24,6 +24,8 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.Cancelable @@ -104,10 +106,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private ) } - override fun updateCanonicalAlias(alias: String, callback: MatrixCallback): Cancelable { + override fun updateCanonicalAlias(alias: String?, altAliases: List, callback: MatrixCallback): Cancelable { return sendStateEvent( eventType = EventType.STATE_ROOM_CANONICAL_ALIAS, - body = mapOf("alias" to alias), + body = RoomCanonicalAliasContent( + canonicalAlias = alias, + alternativeAliases = altAliases + ).toContent(), callback = callback, stateKey = null ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 319947c18e..cf05eb134e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -141,13 +142,13 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { - is RoomAliasAction.AddAlias -> handleAddAlias() - is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) - is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) - RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() + is RoomAliasAction.AddAlias -> handleAddAlias() + is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) + is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) - RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() - is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) }.exhaustive } @@ -160,27 +161,44 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } } - private fun handleAddAlias() { + private fun handleAddAlias() = withState { state -> TODO() } - private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) { + private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) = withState { state -> + updateCanonicalAlias( + state.canonicalAlias, + state.alternativeAliases - action.alias + ) + } + + private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state -> + updateCanonicalAlias( + action.canonicalAlias, + state.alternativeAliases + ) + } + + private fun handleUnsetCanonicalAlias() = withState { state -> + updateCanonicalAlias( + null, + state.alternativeAliases + ) + } + + private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List) { postLoading(true) - viewModelScope.launch { - runCatching { session.deleteRoomAlias(action.alias) } - .onFailure { _viewEvents.post(RoomAliasViewEvents.Failure(it)) } - postLoading(false) + room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback { + override fun onSuccess(data: Unit) { + postLoading(false) + } + + override fun onFailure(failure: Throwable) { + postLoading(false) + _viewEvents.post(RoomAliasViewEvents.Failure(failure)) + } } - } - - private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) { - //room.updateCanonicalAlias() - TODO("Not yet implemented") - } - - private fun handleUnsetCanonicalAlias() { - // room.updateCanonicalAlias() - TODO("Not yet implemented") + ) } private fun handleAddLocalAlias() = withState { state -> From 3c069f8b79fda5a75eef10978d6dae34faebc906 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 17:38:38 +0100 Subject: [PATCH 178/231] Add published aliases --- .../roomprofile/alias/RoomAliasAction.kt | 6 +-- .../roomprofile/alias/RoomAliasController.kt | 52 +++++++++++++++---- .../roomprofile/alias/RoomAliasFragment.kt | 10 ++-- .../roomprofile/alias/RoomAliasViewModel.kt | 23 ++++---- .../roomprofile/alias/RoomAliasViewState.kt | 6 ++- vector/src/main/res/values/strings.xml | 3 +- 6 files changed, 71 insertions(+), 29 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt index a65065f167..ffedaf2539 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -20,10 +20,10 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class RoomAliasAction : VectorViewModelAction { // Canonical - data class AddAlias(val alias: String) : RoomAliasAction() + data class SetNewAlias(val aliasLocalPart: String) : RoomAliasAction() + object AddAlias : RoomAliasAction() data class RemoveAlias(val alias: String) : RoomAliasAction() - data class SetCanonicalAlias(val canonicalAlias: String) : RoomAliasAction() - object UnSetCanonicalAlias : RoomAliasAction() + data class SetCanonicalAlias(val canonicalAlias: String?) : RoomAliasAction() // Local data class RemoveLocalAlias(val alias: String) : RoomAliasAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index cf133da7b7..2737c92fed 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -27,6 +27,7 @@ import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider import im.vector.app.features.discovery.settingsInfoItem +import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formSubmitButtonItem import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter import im.vector.app.features.roomdirectory.createroom.roomAliasEditItem @@ -41,9 +42,10 @@ class RoomAliasController @Inject constructor( ) : TypedEpoxyController() { interface Callback { + fun setNewAlias(value: String) + fun addAlias() fun removeAlias(altAlias: String) - fun setCanonicalAlias(alias: String) - fun unsetCanonicalAlias() + fun setCanonicalAlias(alias: String?) fun removeLocalAlias(alias: String) fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() @@ -67,16 +69,44 @@ class RoomAliasController @Inject constructor( } // TODO Canonical - if (data.alternativeAliases.isNotEmpty()) { + settingsInfoItem { + id("otherPublished") + helperTextResId(R.string.room_alias_published_other) + } + if (data.alternativeAliases.isEmpty()) { settingsInfoItem { - id("otherPublished") - helperTextResId(R.string.room_alias_published_other) + id("otherPublishedEmpty") + if (data.actionPermissions.canChangeCanonicalAlias) { + helperTextResId(R.string.room_alias_address_empty_can_add) + } else { + helperTextResId(R.string.room_alias_address_empty) + } } - data.alternativeAliases.forEachIndexed { idx, altAlias -> - // TODO Rename this item to a more generic name - threePidItem { - id("alt_$idx") - title(altAlias) + } + + if (data.actionPermissions.canChangeCanonicalAlias) { + formEditTextItem { + id("addAlias") + value(data.newAlias) + showBottomSeparator(false) + hint(stringProvider.getString(R.string.room_alias_address_hint)) + onTextChange { text -> + callback?.setNewAlias(text) + } + } + formSubmitButtonItem { + id("submit") + buttonTitleId(R.string.action_add) + buttonClickListener { callback?.addAlias() } + } + } + + data.alternativeAliases.forEachIndexed { idx, altAlias -> + // TODO Rename this item to a more generic name + threePidItem { + id("alt_$idx") + title(altAlias) + if (data.actionPermissions.canChangeCanonicalAlias) { deleteClickListener { callback?.removeAlias(altAlias) } } } @@ -132,7 +162,7 @@ class RoomAliasController @Inject constructor( } formSubmitButtonItem { - id("submit") + id("submitLocal") buttonTitleId(R.string.action_add) buttonClickListener { callback?.addLocalAlias() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 2f7526ebff..7b878b6590 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -112,12 +112,16 @@ class RoomAliasFragment @Inject constructor( .withColoredButton(DialogInterface.BUTTON_POSITIVE) } - override fun setCanonicalAlias(alias: String) { + override fun setCanonicalAlias(alias: String?) { viewModel.handle(RoomAliasAction.SetCanonicalAlias(alias)) } - override fun unsetCanonicalAlias() { - viewModel.handle(RoomAliasAction.UnSetCanonicalAlias) + override fun setNewAlias(value: String) { + viewModel.handle(RoomAliasAction.SetNewAlias(value)) + } + + override fun addAlias() { + viewModel.handle(RoomAliasAction.AddAlias) } override fun setNewLocalAliasLocalPart(value: String) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index cf05eb134e..9d848e98e8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -142,16 +142,25 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { + is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) is RoomAliasAction.AddAlias -> handleAddAlias() is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) - RoomAliasAction.UnSetCanonicalAlias -> handleUnsetCanonicalAlias() is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) }.exhaustive } + private fun handleSetNewAlias(action: RoomAliasAction.SetNewAlias) { + setState { + copy( + newAlias = action.aliasLocalPart, + asyncNewAliasRequest = Uninitialized + ) + } + } + private fun handleSetNewLocalAliasLocalPart(action: RoomAliasAction.SetNewLocalAliasLocalPart) { setState { copy( @@ -162,7 +171,10 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun handleAddAlias() = withState { state -> - TODO() + updateCanonicalAlias( + state.canonicalAlias, + state.alternativeAliases + state.newAlias + ) } private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) = withState { state -> @@ -179,13 +191,6 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo ) } - private fun handleUnsetCanonicalAlias() = withState { state -> - updateCanonicalAlias( - null, - state.alternativeAliases - ) - } - private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List) { postLoading(true) room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index e2021245d8..3e376b75c9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -26,13 +26,15 @@ data class RoomAliasViewState( val roomId: String, val homeServerName: String = "", val roomSummary: Async = Uninitialized, + val actionPermissions: ActionPermissions = ActionPermissions(), val isLoading: Boolean = false, val canonicalAlias: String? = null, val alternativeAliases: List = emptyList(), + val newAlias: String = "", + val asyncNewAliasRequest: Async = Uninitialized, val localAliases: Async> = Uninitialized, val newLocalAlias: String = "", - val asyncNewLocalAliasRequest: Async = Uninitialized, - val actionPermissions: ActionPermissions = ActionPermissions() + val asyncNewLocalAliasRequest: Async = Uninitialized ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index c97785e74c..33b4575435 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1035,7 +1035,8 @@ Delete the address \"%1$s\"? Publish this room to the public in %1$s\'s room directory? - No other published addresses yet, add one below + No other published addresses yet, add one below. + No other published addresses yet. New published address (e.g. #alias:server) Local Addresses From 893ebd969059c5198d4ade3a30d12267d761263e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 17:54:14 +0100 Subject: [PATCH 179/231] Fix UI error --- .../roomprofile/alias/RoomAliasController.kt | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index 2737c92fed..f51cd610c2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -60,6 +60,13 @@ class RoomAliasController @Inject constructor( override fun buildModels(data: RoomAliasViewState?) { data ?: return + // Published + buildPublishInfo(data) + // Local + buildLocalInfo(data) + } + + private fun buildPublishInfo(data: RoomAliasViewState) { buildProfileSection( stringProvider.getString(R.string.room_alias_published_alias_title) ) @@ -68,11 +75,8 @@ class RoomAliasController @Inject constructor( helperTextResId(R.string.room_alias_published_alias_subtitle) } - // TODO Canonical - settingsInfoItem { - id("otherPublished") - helperTextResId(R.string.room_alias_published_other) - } + // TODO Set/Unset Canonical + if (data.alternativeAliases.isEmpty()) { settingsInfoItem { id("otherPublishedEmpty") @@ -82,6 +86,21 @@ class RoomAliasController @Inject constructor( helperTextResId(R.string.room_alias_address_empty) } } + } else { + settingsInfoItem { + id("otherPublished") + helperTextResId(R.string.room_alias_published_other) + } + data.alternativeAliases.forEachIndexed { idx, altAlias -> + // TODO Rename this item to a more generic name + threePidItem { + id("alt_$idx") + title(altAlias) + if (data.actionPermissions.canChangeCanonicalAlias) { + deleteClickListener { callback?.removeAlias(altAlias) } + } + } + } } if (data.actionPermissions.canChangeCanonicalAlias) { @@ -100,20 +119,6 @@ class RoomAliasController @Inject constructor( buttonClickListener { callback?.addAlias() } } } - - data.alternativeAliases.forEachIndexed { idx, altAlias -> - // TODO Rename this item to a more generic name - threePidItem { - id("alt_$idx") - title(altAlias) - if (data.actionPermissions.canChangeCanonicalAlias) { - deleteClickListener { callback?.removeAlias(altAlias) } - } - } - } - - // Local - buildLocalInfo(data) } private fun buildLocalInfo(data: RoomAliasViewState) { From ed4676bb6c859b7382e2268acf25b5cd8e9c62f3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 23 Nov 2020 17:59:25 +0100 Subject: [PATCH 180/231] Cleanup and avoid duplicate --- .../session/room/state/DefaultStateService.kt | 2 +- .../roomprofile/alias/RoomAliasViewModel.kt | 23 +++++++++++-------- .../roomprofile/alias/RoomAliasViewState.kt | 1 - 3 files changed, 15 insertions(+), 11 deletions(-) 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 8fa0857837..96fb71503a 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 @@ -111,7 +111,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private eventType = EventType.STATE_ROOM_CANONICAL_ALIAS, body = RoomCanonicalAliasContent( canonicalAlias = alias, - alternativeAliases = altAliases + alternativeAliases = altAliases.distinct() ).toContent(), callback = callback, stateKey = null diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 9d848e98e8..9c8a7f9df8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -142,21 +142,20 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { - is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) - is RoomAliasAction.AddAlias -> handleAddAlias() - is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) - is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) + is RoomAliasAction.AddAlias -> handleAddAlias() + is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) + is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) - RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() - is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) }.exhaustive } private fun handleSetNewAlias(action: RoomAliasAction.SetNewAlias) { setState { copy( - newAlias = action.aliasLocalPart, - asyncNewAliasRequest = Uninitialized + newAlias = action.aliasLocalPart ) } } @@ -195,7 +194,12 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo postLoading(true) room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback { override fun onSuccess(data: Unit) { - postLoading(false) + setState { + copy( + isLoading = false, + newAlias = "" + ) + } } override fun onFailure(failure: Throwable) { @@ -228,6 +232,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo setState { copy( isLoading = false, + newLocalAlias = "", asyncNewLocalAliasRequest = Uninitialized ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index 3e376b75c9..03e3843aab 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -31,7 +31,6 @@ data class RoomAliasViewState( val canonicalAlias: String? = null, val alternativeAliases: List = emptyList(), val newAlias: String = "", - val asyncNewAliasRequest: Async = Uninitialized, val localAliases: Async> = Uninitialized, val newLocalAlias: String = "", val asyncNewLocalAliasRequest: Async = Uninitialized From 3a06ef395904fd9ea9c7d761013a0be0c3e979f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:04:44 +0100 Subject: [PATCH 181/231] Improve form UX + local echo in case of success --- .../discovery/SettingsContinueCancelItem.kt | 5 + .../roomprofile/alias/RoomAliasAction.kt | 2 + .../roomprofile/alias/RoomAliasController.kt | 92 +++++++++++++------ .../roomprofile/alias/RoomAliasFragment.kt | 8 ++ .../roomprofile/alias/RoomAliasViewModel.kt | 72 ++++++++++++--- .../roomprofile/alias/RoomAliasViewState.kt | 11 ++- vector/src/main/res/values/strings.xml | 3 + 7 files changed, 149 insertions(+), 44 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt b/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt index c9ad23f1a9..b59b24fe55 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/SettingsContinueCancelItem.kt @@ -27,6 +27,9 @@ import im.vector.app.core.epoxy.onClick @EpoxyModelClass(layout = R.layout.item_settings_continue_cancel) abstract class SettingsContinueCancelItem : EpoxyModelWithHolder() { + @EpoxyAttribute + var continueText: String? = null + @EpoxyAttribute var continueOnClick: ClickListener? = null @@ -37,6 +40,8 @@ abstract class SettingsContinueCancelItem : EpoxyModelWithHolder() { interface Callback { + fun toggleManualPublishForm() fun setNewAlias(value: String) fun addAlias() fun removeAlias(altAlias: String) fun setCanonicalAlias(alias: String?) fun removeLocalAlias(alias: String) + fun toggleLocalAliasForm() fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() } @@ -104,19 +109,37 @@ class RoomAliasController @Inject constructor( } if (data.actionPermissions.canChangeCanonicalAlias) { - formEditTextItem { - id("addAlias") - value(data.newAlias) - showBottomSeparator(false) - hint(stringProvider.getString(R.string.room_alias_address_hint)) - onTextChange { text -> - callback?.setNewAlias(text) + buildPublishManuallyForm(data) + } + } + + private fun buildPublishManuallyForm(data: RoomAliasViewState) { + when (data.publishManuallyState) { + RoomAliasViewState.AddAliasState.Hidden -> Unit + RoomAliasViewState.AddAliasState.Closed -> { + settingsButtonItem { + id("publishManually") + colorProvider(colorProvider) + buttonTitleId(R.string.room_alias_published_alias_add_manually) + buttonClickListener { callback?.toggleManualPublishForm() } } } - formSubmitButtonItem { - id("submit") - buttonTitleId(R.string.action_add) - buttonClickListener { callback?.addAlias() } + is RoomAliasViewState.AddAliasState.Editing -> { + formEditTextItem { + id("publishManuallyEdit") + value(data.publishManuallyState.value) + showBottomSeparator(false) + hint(stringProvider.getString(R.string.room_alias_address_hint)) + onTextChange { text -> + callback?.setNewAlias(text) + } + } + settingsContinueCancelItem { + id("publishManuallySubmit") + continueText(stringProvider.getString(R.string.room_alias_published_alias_add_manually_submit)) + continueOnClick { callback?.addAlias() } + cancelOnClick { callback?.toggleManualPublishForm() } + } } } } @@ -155,21 +178,38 @@ class RoomAliasController @Inject constructor( } // Add local - roomAliasEditItem { - id("newLocalAlias") - value(data.newLocalAlias) - homeServer(":" + data.homeServerName) - showBottomSeparator(false) - errorMessage(roomAliasErrorFormatter.format((data.asyncNewLocalAliasRequest as? Fail)?.error as? RoomAliasError)) - onTextChange { value -> - callback?.setNewLocalAliasLocalPart(value) - } - } + buildAddLocalAlias(data) + } - formSubmitButtonItem { - id("submitLocal") - buttonTitleId(R.string.action_add) - buttonClickListener { callback?.addLocalAlias() } + private fun buildAddLocalAlias(data: RoomAliasViewState) { + when (data.newLocalAliasState) { + RoomAliasViewState.AddAliasState.Hidden -> Unit + RoomAliasViewState.AddAliasState.Closed -> { + settingsButtonItem { + id("newLocalAliasButton") + colorProvider(colorProvider) + buttonTitleId(R.string.room_alias_local_address_add) + buttonClickListener { callback?.toggleLocalAliasForm() } + } + } + is RoomAliasViewState.AddAliasState.Editing -> { + roomAliasEditItem { + id("newLocalAlias") + value(data.newLocalAliasState.value) + homeServer(":" + data.homeServerName) + showBottomSeparator(false) + errorMessage(roomAliasErrorFormatter.format((data.newLocalAliasState.asyncRequest as? Fail)?.error as? RoomAliasError)) + onTextChange { value -> + callback?.setNewLocalAliasLocalPart(value) + } + } + settingsContinueCancelItem { + id("newLocalAliasSubmit") + continueText(stringProvider.getString(R.string.action_add)) + continueOnClick { callback?.addLocalAlias() } + cancelOnClick { callback?.toggleLocalAliasForm() } + } + } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 7b878b6590..20ea7a8901 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -116,6 +116,10 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.SetCanonicalAlias(alias)) } + override fun toggleManualPublishForm() { + viewModel.handle(RoomAliasAction.ToggleManualPublishForm) + } + override fun setNewAlias(value: String) { viewModel.handle(RoomAliasAction.SetNewAlias(value)) } @@ -124,6 +128,10 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.AddAlias) } + override fun toggleLocalAliasForm() { + viewModel.handle(RoomAliasAction.ToggleAddLocalAliasForm) + } + override fun setNewLocalAliasLocalPart(value: String) { viewModel.handle(RoomAliasAction.SetNewLocalAliasLocalPart(value)) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 9c8a7f9df8..a4f0d97818 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -142,20 +142,46 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { + RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm() is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) is RoomAliasAction.AddAlias -> handleAddAlias() is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm() is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) }.exhaustive } + private fun handleToggleAddLocalAliasForm() { + setState { + copy( + newLocalAliasState = when (newLocalAliasState) { + RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Hidden + RoomAliasViewState.AddAliasState.Closed -> RoomAliasViewState.AddAliasState.Editing("", Uninitialized) + is RoomAliasViewState.AddAliasState.Editing -> RoomAliasViewState.AddAliasState.Closed + } + ) + } + } + + private fun handleToggleManualPublishForm() { + setState { + copy( + publishManuallyState = when (publishManuallyState) { + RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Hidden + RoomAliasViewState.AddAliasState.Closed -> RoomAliasViewState.AddAliasState.Editing("", Uninitialized) + is RoomAliasViewState.AddAliasState.Editing -> RoomAliasViewState.AddAliasState.Closed + } + ) + } + } + private fun handleSetNewAlias(action: RoomAliasAction.SetNewAlias) { setState { copy( - newAlias = action.aliasLocalPart + publishManuallyState = RoomAliasViewState.AddAliasState.Editing(action.aliasLocalPart, Uninitialized) ) } } @@ -163,16 +189,16 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleSetNewLocalAliasLocalPart(action: RoomAliasAction.SetNewLocalAliasLocalPart) { setState { copy( - newLocalAlias = action.aliasLocalPart, - asyncNewLocalAliasRequest = Uninitialized + newLocalAliasState = RoomAliasViewState.AddAliasState.Editing(action.aliasLocalPart, Uninitialized) ) } } private fun handleAddAlias() = withState { state -> + val newAlias = (state.newLocalAliasState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState updateCanonicalAlias( state.canonicalAlias, - state.alternativeAliases + state.newAlias + state.alternativeAliases + newAlias ) } @@ -197,7 +223,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo setState { copy( isLoading = false, - newAlias = "" + publishManuallyState = RoomAliasViewState.AddAliasState.Closed ) } } @@ -206,24 +232,25 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo postLoading(false) _viewEvents.post(RoomAliasViewEvents.Failure(failure)) } - } - ) + }) } private fun handleAddLocalAlias() = withState { state -> + val previousState = (state.newLocalAliasState as? RoomAliasViewState.AddAliasState.Editing) ?: return@withState + setState { copy( isLoading = true, - asyncNewLocalAliasRequest = Loading() + newLocalAliasState = previousState.copy(asyncRequest = Loading()) ) } viewModelScope.launch { - runCatching { room.addAlias(state.newLocalAlias) } + runCatching { room.addAlias(previousState.value) } .onFailure { setState { copy( isLoading = false, - asyncNewLocalAliasRequest = Fail(it) + newLocalAliasState = previousState.copy(asyncRequest = Fail(it)) ) } _viewEvents.post(RoomAliasViewEvents.Failure(it)) @@ -232,8 +259,9 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo setState { copy( isLoading = false, - newLocalAlias = "", - asyncNewLocalAliasRequest = Uninitialized + newLocalAliasState = RoomAliasViewState.AddAliasState.Closed, + // Local echo + localAliases = Success(localAliases().orEmpty() + previousState.value) ) } fetchRoomAlias() @@ -245,9 +273,23 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo postLoading(true) viewModelScope.launch { runCatching { session.deleteRoomAlias(action.alias) } - .onFailure { _viewEvents.post(RoomAliasViewEvents.Failure(it)) } - .onSuccess { fetchRoomAlias() } - postLoading(false) + .onFailure { + setState { + copy(isLoading = false) + } + _viewEvents.post(RoomAliasViewEvents.Failure(it)) + } + .onSuccess { + // Local echo + setState { + copy( + isLoading = false, + // Local echo + localAliases = Success(localAliases().orEmpty() - action.alias) + ) + } + fetchRoomAlias() + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index 03e3843aab..114b69c1ad 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -30,10 +30,9 @@ data class RoomAliasViewState( val isLoading: Boolean = false, val canonicalAlias: String? = null, val alternativeAliases: List = emptyList(), - val newAlias: String = "", + val publishManuallyState: AddAliasState = AddAliasState.Hidden, val localAliases: Async> = Uninitialized, - val newLocalAlias: String = "", - val asyncNewLocalAliasRequest: Async = Uninitialized + val newLocalAliasState: AddAliasState = AddAliasState.Hidden ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) @@ -41,4 +40,10 @@ data class RoomAliasViewState( data class ActionPermissions( val canChangeCanonicalAlias: Boolean = false ) + + sealed class AddAliasState { + object Hidden : AddAliasState() + object Closed : AddAliasState() + data class Editing(val value: String, val asyncRequest: Async = Uninitialized) : AddAliasState() + } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 33b4575435..36f2b38dab 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1032,6 +1032,8 @@ Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first. Main address Other published addresses: + Published a new address manually + Publish Delete the address \"%1$s\"? Publish this room to the public in %1$s\'s room directory? @@ -1043,6 +1045,7 @@ Set addresses for this room so users can find this room through your homeserver (%1$s) This room has no local addresses + Add a local address Anyone From 0a9b23427274b133df01db96a21f8d9262de407d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:05:25 +0100 Subject: [PATCH 182/231] Renaming --- .../im/vector/app/features/roomprofile/alias/RoomAliasAction.kt | 2 +- .../vector/app/features/roomprofile/alias/RoomAliasViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt index ffea09f5fd..b695ab0a1e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class RoomAliasAction : VectorViewModelAction { // Canonical object ToggleManualPublishForm : RoomAliasAction() - data class SetNewAlias(val aliasLocalPart: String) : RoomAliasAction() + data class SetNewAlias(val alias: String) : RoomAliasAction() object AddAlias : RoomAliasAction() data class RemoveAlias(val alias: String) : RoomAliasAction() data class SetCanonicalAlias(val canonicalAlias: String?) : RoomAliasAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index a4f0d97818..89ede6e825 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -181,7 +181,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleSetNewAlias(action: RoomAliasAction.SetNewAlias) { setState { copy( - publishManuallyState = RoomAliasViewState.AddAliasState.Editing(action.aliasLocalPart, Uninitialized) + publishManuallyState = RoomAliasViewState.AddAliasState.Editing(action.alias, Uninitialized) ) } } From 82b23d9a1390f7e9553df4e8a001a48f37072920 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:08:39 +0100 Subject: [PATCH 183/231] Ensure we push only clean m.room.canonical_alias event --- .../internal/session/room/state/DefaultStateService.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 96fb71503a..3d6e869607 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 @@ -111,7 +111,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private eventType = EventType.STATE_ROOM_CANONICAL_ALIAS, body = RoomCanonicalAliasContent( canonicalAlias = alias, - alternativeAliases = altAliases.distinct() + alternativeAliases = altAliases + // Ensure there is no duplicate + .distinct() + // Ensure the canonical alias is not also included in the alt alias + .minus(listOfNotNull(alias)) + // Sort for the cleanup + .sorted() ).toContent(), callback = callback, stateKey = null From 74ffbd46793ee66c8ec1c37221cd23460f1ff88e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:14:58 +0100 Subject: [PATCH 184/231] Ensure the forms are displayable --- .../roomprofile/alias/RoomAliasViewModel.kt | 15 ++++++++++++++- .../roomprofile/alias/RoomAliasViewState.kt | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 89ede6e825..e4d0c0c069 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -116,7 +116,20 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_CANONICAL_ALIAS), ) - setState { copy(actionPermissions = permissions) } + setState { + val newPublishManuallyState = if (permissions.canChangeCanonicalAlias) { + when (publishManuallyState) { + RoomAliasViewState.AddAliasState.Hidden -> RoomAliasViewState.AddAliasState.Closed + else -> publishManuallyState + } + } else { + RoomAliasViewState.AddAliasState.Hidden + } + copy( + actionPermissions = permissions, + publishManuallyState = newPublishManuallyState + ) + } } .disposeOnClear() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index 114b69c1ad..e1736296c6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -32,7 +32,7 @@ data class RoomAliasViewState( val alternativeAliases: List = emptyList(), val publishManuallyState: AddAliasState = AddAliasState.Hidden, val localAliases: Async> = Uninitialized, - val newLocalAliasState: AddAliasState = AddAliasState.Hidden + val newLocalAliasState: AddAliasState = AddAliasState.Closed ) : MvRxState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) From 8dbb984ead061f12f2cf7911cf73c960d7701d5b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:18:17 +0100 Subject: [PATCH 185/231] Sort the aliases --- .../app/features/roomprofile/alias/RoomAliasViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index e4d0c0c069..92f635482f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -88,7 +88,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo runCatching { room.getRoomAliases() } .fold( { - setState { copy(localAliases = Success(it)) } + setState { copy(localAliases = Success(it.sorted())) } }, { setState { copy(localAliases = Fail(it)) } @@ -146,7 +146,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo setState { copy( canonicalAlias = it.canonicalAlias, - alternativeAliases = it.alternativeAliases.orEmpty() + alternativeAliases = it.alternativeAliases.orEmpty().sorted() ) } } @@ -274,7 +274,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo isLoading = false, newLocalAliasState = RoomAliasViewState.AddAliasState.Closed, // Local echo - localAliases = Success(localAliases().orEmpty() + previousState.value) + localAliases = Success((localAliases().orEmpty() + previousState.value).sorted()) ) } fetchRoomAlias() From 36564f0c7520d64e769f85bd827ede9abbdb1d9e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:20:12 +0100 Subject: [PATCH 186/231] copy/paste error --- .../vector/app/features/roomprofile/alias/RoomAliasViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 92f635482f..fac8d52159 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -208,7 +208,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } private fun handleAddAlias() = withState { state -> - val newAlias = (state.newLocalAliasState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState + val newAlias = (state.publishManuallyState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState updateCanonicalAlias( state.canonicalAlias, state.alternativeAliases + newAlias From 2cf2233643e455c7c2552be17dc30f30b9a43a83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:23:02 +0100 Subject: [PATCH 187/231] Naming convention --- ...ction.kt => RoomBannedMemberListAction.kt} | 8 +++---- .../banned/RoomBannedMemberListFragment.kt | 14 +++++------ ...s.kt => RoomBannedMemberListViewEvents.kt} | 6 ++--- ...el.kt => RoomBannedMemberListViewModel.kt} | 24 +++++++++---------- 4 files changed, 26 insertions(+), 26 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/banned/{RoomBannedListMemberAction.kt => RoomBannedMemberListAction.kt} (82%) rename vector/src/main/java/im/vector/app/features/roomprofile/banned/{RoomBannedViewEvents.kt => RoomBannedMemberListViewEvents.kt} (83%) rename vector/src/main/java/im/vector/app/features/roomprofile/banned/{RoomBannedListMemberViewModel.kt => RoomBannedMemberListViewModel.kt} (84%) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListAction.kt similarity index 82% rename from vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListAction.kt index ca7d567d90..8f6f5afba1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListAction.kt @@ -19,8 +19,8 @@ package im.vector.app.features.roomprofile.banned import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -sealed class RoomBannedListMemberAction : VectorViewModelAction { - data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction() - data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedListMemberAction() - data class Filter(val filter: String) : RoomBannedListMemberAction() +sealed class RoomBannedMemberListAction : VectorViewModelAction { + data class QueryInfo(val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListAction() + data class UnBanUser(val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListAction() + data class Filter(val filter: String) : RoomBannedMemberListAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt index 797e6c8aa3..349321c87a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt @@ -37,18 +37,18 @@ import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class RoomBannedMemberListFragment @Inject constructor( - val viewModelFactory: RoomBannedListMemberViewModel.Factory, + val viewModelFactory: RoomBannedMemberListViewModel.Factory, private val roomMemberListController: RoomBannedMemberListController, private val avatarRenderer: AvatarRenderer ) : VectorBaseFragment(), RoomBannedMemberListController.Callback { - private val viewModel: RoomBannedListMemberViewModel by fragmentViewModel() + private val viewModel: RoomBannedMemberListViewModel by fragmentViewModel() private val roomProfileArgs: RoomProfileArgs by args() override fun getLayoutResId() = R.layout.fragment_room_setting_generic override fun onUnbanClicked(roomMember: RoomMemberSummary) { - viewModel.handle(RoomBannedListMemberAction.QueryInfo(roomMember)) + viewModel.handle(RoomBannedMemberListAction.QueryInfo(roomMember)) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -60,7 +60,7 @@ class RoomBannedMemberListFragment @Inject constructor( viewModel.observeViewEvents { when (it) { - is RoomBannedViewEvents.ShowBannedInfo -> { + is RoomBannedMemberListViewEvents.ShowBannedInfo -> { val canBan = withState(viewModel) { state -> state.canUserBan } AlertDialog.Builder(requireActivity()) .setTitle(getString(R.string.member_banned_by, it.bannedByUserId)) @@ -69,13 +69,13 @@ class RoomBannedMemberListFragment @Inject constructor( .apply { if (canBan) { setNegativeButton(R.string.room_participants_action_unban) { _, _ -> - viewModel.handle(RoomBannedListMemberAction.UnBanUser(it.roomMemberSummary)) + viewModel.handle(RoomBannedMemberListAction.UnBanUser(it.roomMemberSummary)) } } } .show() } - is RoomBannedViewEvents.ToastError -> { + is RoomBannedMemberListViewEvents.ToastError -> { requireActivity().toast(it.info) } } @@ -96,7 +96,7 @@ class RoomBannedMemberListFragment @Inject constructor( } override fun onQueryTextChange(newText: String): Boolean { - viewModel.handle(RoomBannedListMemberAction.Filter(newText)) + viewModel.handle(RoomBannedMemberListAction.Filter(newText)) return true } }) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedViewEvents.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewEvents.kt similarity index 83% rename from vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedViewEvents.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewEvents.kt index 6b59debe96..4b1dc018ee 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewEvents.kt @@ -19,7 +19,7 @@ package im.vector.app.features.roomprofile.banned import im.vector.app.core.platform.VectorViewEvents import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -sealed class RoomBannedViewEvents : VectorViewEvents { - data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedViewEvents() - data class ToastError(val info: String) : RoomBannedViewEvents() +sealed class RoomBannedMemberListViewEvents : VectorViewEvents { + data class ShowBannedInfo(val bannedByUserId: String, val banReason: String, val roomMemberSummary: RoomMemberSummary) : RoomBannedMemberListViewEvents() + data class ToastError(val info: String) : RoomBannedMemberListViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt similarity index 84% rename from vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 1cce2f96cb..0cecd22fa0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedListMemberViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -42,14 +42,14 @@ import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap -class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, +class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomBannedMemberListViewState, private val stringProvider: StringProvider, private val session: Session) - : VectorViewModel(initialState) { + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { - fun create(initialState: RoomBannedMemberListViewState): RoomBannedListMemberViewModel + fun create(initialState: RoomBannedMemberListViewState): RoomBannedMemberListViewModel } private val room = session.getRoom(initialState.roomId)!! @@ -78,24 +78,24 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia }.disposeOnClear() } - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedListMemberViewModel? { + override fun create(viewModelContext: ViewModelContext, state: RoomBannedMemberListViewState): RoomBannedMemberListViewModel? { val fragment: RoomBannedMemberListFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.viewModelFactory.create(state) } } - override fun handle(action: RoomBannedListMemberAction) { + override fun handle(action: RoomBannedMemberListAction) { when (action) { - is RoomBannedListMemberAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary) - is RoomBannedListMemberAction.UnBanUser -> unBanUser(action.roomMemberSummary) - is RoomBannedListMemberAction.Filter -> handleFilter(action) + is RoomBannedMemberListAction.QueryInfo -> onQueryBanInfo(action.roomMemberSummary) + is RoomBannedMemberListAction.UnBanUser -> unBanUser(action.roomMemberSummary) + is RoomBannedMemberListAction.Filter -> handleFilter(action) }.exhaustive } - private fun handleFilter(action: RoomBannedListMemberAction.Filter) { + private fun handleFilter(action: RoomBannedMemberListAction.Filter) { setState { copy( filter = action.filter @@ -114,7 +114,7 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia val reason = content.reason val bannedBy = bannedEvent?.senderId ?: return - _viewEvents.post(RoomBannedViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary)) + _viewEvents.post(RoomBannedMemberListViewEvents.ShowBannedInfo(bannedBy, reason ?: "", roomMemberSummary)) } private fun unBanUser(roomMemberSummary: RoomMemberSummary) { @@ -127,7 +127,7 @@ class RoomBannedListMemberViewModel @AssistedInject constructor(@Assisted initia room.unban(roomMemberSummary.userId, null, it) } } catch (failure: Throwable) { - _viewEvents.post(RoomBannedViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban))) + _viewEvents.post(RoomBannedMemberListViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban))) } finally { setState { copy( From 8f80f375f01afc4bb912af18e5ecee12c3d33616 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:37:55 +0100 Subject: [PATCH 188/231] Prepare alias bottom sheet --- .../app/core/epoxy/profiles/ProfileActionItem.kt | 6 ++++-- .../core/epoxy/profiles/ProfileItemExtensions.kt | 4 +--- .../roomprofile/alias/RoomAliasController.kt | 16 +++++++--------- .../roomprofile/alias/RoomAliasFragment.kt | 4 ++++ 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt index 3bef38d4cb..f52051f989 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt @@ -26,8 +26,10 @@ import androidx.core.widget.ImageViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.themes.ThemeUtils @@ -67,11 +69,11 @@ abstract class ProfileActionItem : VectorEpoxyModel() var destructive: Boolean = false @EpoxyAttribute - var listener: View.OnClickListener? = null + var listener: ClickListener? = null override fun bind(holder: Holder) { super.bind(holder) - holder.view.setOnClickListener(listener) + holder.view.onClick(listener) if (listener == null) { holder.view.isClickable = false } diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt index fdbe9f7f94..99acd6cb36 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileItemExtensions.kt @@ -59,9 +59,7 @@ fun EpoxyController.buildProfileAction( accessoryRes(accessory) accessoryMatrixItem(accessoryMatrixItem) avatarRenderer(avatarRenderer) - listener { _ -> - action?.invoke() - } + listener(action) } if (divider) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index fe19f65629..a8c1222a5f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -24,6 +24,7 @@ import im.vector.app.R import im.vector.app.core.epoxy.errorWithRetryItem import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.profiles.buildProfileSection +import im.vector.app.core.epoxy.profiles.profileActionItem import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider @@ -33,7 +34,6 @@ import im.vector.app.features.discovery.settingsInfoItem import im.vector.app.features.form.formEditTextItem import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter import im.vector.app.features.roomdirectory.createroom.roomAliasEditItem -import im.vector.app.features.settings.threepids.threePidItem import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import javax.inject.Inject @@ -48,12 +48,14 @@ class RoomAliasController @Inject constructor( fun toggleManualPublishForm() fun setNewAlias(value: String) fun addAlias() + // TODO Delete some methods below fun removeAlias(altAlias: String) fun setCanonicalAlias(alias: String?) fun removeLocalAlias(alias: String) fun toggleLocalAliasForm() fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() + fun openAlias(alias: String, isPublished: Boolean) } var callback: Callback? = null @@ -97,13 +99,10 @@ class RoomAliasController @Inject constructor( helperTextResId(R.string.room_alias_published_other) } data.alternativeAliases.forEachIndexed { idx, altAlias -> - // TODO Rename this item to a more generic name - threePidItem { + profileActionItem { id("alt_$idx") title(altAlias) - if (data.actionPermissions.canChangeCanonicalAlias) { - deleteClickListener { callback?.removeAlias(altAlias) } - } + listener { callback?.openAlias(altAlias, true) } } } } @@ -161,11 +160,10 @@ class RoomAliasController @Inject constructor( } is Success -> { localAliases().forEachIndexed { idx, localAlias -> - // TODO Rename this item to a more generic name - threePidItem { + profileActionItem { id("loc_$idx") title(localAlias) - deleteClickListener { callback?.removeLocalAlias(localAlias) } + listener { callback?.openAlias(localAlias, false) } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 20ea7a8901..b14783d9cd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -140,6 +140,10 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.AddLocalAlias) } + override fun openAlias(alias: String, isPublished: Boolean) { + TODO() + } + override fun removeLocalAlias(alias: String) { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_confirmation) From d9c209aa87a2c7ab377adf9b61ccc28413095ad8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 06:49:42 +0100 Subject: [PATCH 189/231] Cleanup --- .../home/room/list/actions/RoomListQuickActionsBottomSheet.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt index e3a5db4b97..f41104cae1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/actions/RoomListQuickActionsBottomSheet.kt @@ -77,6 +77,7 @@ class RoomListQuickActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), R override fun onDestroyView() { recyclerView.cleanup() + roomListActionsEpoxyController.listener = null super.onDestroyView() } From c5e6e004dd416bd96a5806909e18dc89ba4ab546 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 07:59:57 +0100 Subject: [PATCH 190/231] Room alias detail --- .idea/dictionaries/bmarty.xml | 1 + .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../im/vector/app/core/di/ViewModelModule.kt | 6 + .../roomprofile/alias/RoomAliasAction.kt | 5 +- .../roomprofile/alias/RoomAliasController.kt | 4 - .../roomprofile/alias/RoomAliasFragment.kt | 54 +++++++-- .../roomprofile/alias/RoomAliasViewModel.kt | 26 +++-- .../alias/detail/RoomAliasBottomSheet.kt | 104 ++++++++++++++++++ .../detail/RoomAliasBottomSheetController.kt | 85 ++++++++++++++ .../RoomAliasBottomSheetSharedAction.kt | 56 ++++++++++ ...omAliasBottomSheetSharedActionViewModel.kt | 25 +++++ .../alias/detail/RoomAliasBottomSheetState.kt | 35 ++++++ .../detail/RoomAliasBottomSheetViewModel.kt | 58 ++++++++++ vector/src/main/res/drawable/ic_trash_24.xml | 76 +++++++------ vector/src/main/res/values/strings.xml | 3 + 15 files changed, 479 insertions(+), 61 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml index d13e40248f..5ad39614b7 100644 --- a/.idea/dictionaries/bmarty.xml +++ b/.idea/dictionaries/bmarty.xml @@ -31,6 +31,7 @@ ssss sygnal threepid + unpublish unwedging diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 818a32fca3..2518e32ce5 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -67,6 +67,7 @@ import im.vector.app.features.roomdirectory.createroom.CreateRoomActivity import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roomprofile.RoomProfileActivity +import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.share.IncomingShareActivity @@ -153,6 +154,7 @@ interface ScreenComponent { fun inject(bottomSheet: ViewEditHistoryBottomSheet) fun inject(bottomSheet: DisplayReadReceiptsBottomSheet) fun inject(bottomSheet: RoomListQuickActionsBottomSheet) + fun inject(bottomSheet: RoomAliasBottomSheet) fun inject(bottomSheet: VerificationBottomSheet) fun inject(bottomSheet: DeviceVerificationInfoBottomSheet) fun inject(bottomSheet: DeviceListBottomSheet) diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 7ae8bc9c2e..3399f98d43 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -35,6 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.reactions.EmojiChooserViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel +import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel @Module @@ -105,6 +106,11 @@ interface ViewModelModule { @ViewModelKey(RoomListQuickActionsSharedActionViewModel::class) fun bindRoomListQuickActionsSharedActionViewModel(viewModel: RoomListQuickActionsSharedActionViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(RoomAliasBottomSheetSharedActionViewModel::class) + fun bindRoomAliasBottomSheetSharedActionViewModel(viewModel: RoomAliasBottomSheetSharedActionViewModel): ViewModel + @Binds @IntoMap @ViewModelKey(RoomDirectorySharedActionViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt index b695ab0a1e..4054d6f63a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -22,8 +22,9 @@ sealed class RoomAliasAction : VectorViewModelAction { // Canonical object ToggleManualPublishForm : RoomAliasAction() data class SetNewAlias(val alias: String) : RoomAliasAction() - object AddAlias : RoomAliasAction() - data class RemoveAlias(val alias: String) : RoomAliasAction() + object ManualPublishAlias : RoomAliasAction() + data class PublishAlias(val alias: String) : RoomAliasAction() + data class UnpublishAlias(val alias: String) : RoomAliasAction() data class SetCanonicalAlias(val canonicalAlias: String?) : RoomAliasAction() // Local diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index a8c1222a5f..6790bee2a8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -48,10 +48,6 @@ class RoomAliasController @Inject constructor( fun toggleManualPublishForm() fun setNewAlias(value: String) fun addAlias() - // TODO Delete some methods below - fun removeAlias(altAlias: String) - fun setCanonicalAlias(alias: String?) - fun removeLocalAlias(alias: String) fun toggleLocalAliasForm() fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index b14783d9cd..f54cff5c6c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -30,9 +30,13 @@ import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.utils.shareText import im.vector.app.core.utils.toast import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet +import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedAction +import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.matrix.android.sdk.api.session.room.alias.RoomAliasError @@ -48,12 +52,16 @@ class RoomAliasFragment @Inject constructor( RoomAliasController.Callback { private val viewModel: RoomAliasViewModel by fragmentViewModel() + private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel + private val roomProfileArgs: RoomProfileArgs by args() override fun getLayoutResId() = R.layout.fragment_room_setting_generic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java) + controller.callback = this setupToolbar(roomSettingsToolbar) roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) @@ -63,9 +71,30 @@ class RoomAliasFragment @Inject constructor( viewModel.observeViewEvents { when (it) { is RoomAliasViewEvents.Failure -> showFailure(it.throwable) - RoomAliasViewEvents.Success -> showSuccess() + RoomAliasViewEvents.Success -> showSuccess() }.exhaustive } + + sharedActionViewModel + .observe() + .subscribe { handleAliasAction(it) } + .disposeOnDestroyView() + } + + private fun handleAliasAction(action: RoomAliasBottomSheetSharedAction?) { + when (action) { + is RoomAliasBottomSheetSharedAction.ShareAlias -> shareAlias(action.matrixTo) + is RoomAliasBottomSheetSharedAction.PublishAlias -> viewModel.handle(RoomAliasAction.PublishAlias(action.alias)) + is RoomAliasBottomSheetSharedAction.UnPublishAlias -> unpublishAlias(action.alias) + is RoomAliasBottomSheetSharedAction.DeleteAlias -> removeLocalAlias(action.alias) + is RoomAliasBottomSheetSharedAction.SetMainAlias -> viewModel.handle(RoomAliasAction.SetCanonicalAlias(action.alias)) + RoomAliasBottomSheetSharedAction.UnsetMainAlias -> viewModel.handle(RoomAliasAction.SetCanonicalAlias(canonicalAlias = null)) + null -> Unit + } + } + + private fun shareAlias(matrixTo: String) { + shareText(requireContext(), matrixTo) } override fun showFailure(throwable: Throwable) { @@ -100,22 +129,18 @@ class RoomAliasFragment @Inject constructor( invalidateOptionsMenu() } - override fun removeAlias(altAlias: String) { + private fun unpublishAlias(altAlias: String) { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_confirmation) .setMessage(getString(R.string.room_alias_delete_confirmation, altAlias)) .setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.delete) { _, _ -> - viewModel.handle(RoomAliasAction.RemoveAlias(altAlias)) + viewModel.handle(RoomAliasAction.UnpublishAlias(altAlias)) } .show() .withColoredButton(DialogInterface.BUTTON_POSITIVE) } - override fun setCanonicalAlias(alias: String?) { - viewModel.handle(RoomAliasAction.SetCanonicalAlias(alias)) - } - override fun toggleManualPublishForm() { viewModel.handle(RoomAliasAction.ToggleManualPublishForm) } @@ -125,7 +150,7 @@ class RoomAliasFragment @Inject constructor( } override fun addAlias() { - viewModel.handle(RoomAliasAction.AddAlias) + viewModel.handle(RoomAliasAction.ManualPublishAlias) } override fun toggleLocalAliasForm() { @@ -140,11 +165,18 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.AddLocalAlias) } - override fun openAlias(alias: String, isPublished: Boolean) { - TODO() + override fun openAlias(alias: String, isPublished: Boolean) = withState(viewModel) { state -> + RoomAliasBottomSheet + .newInstance( + alias = alias, + isPublished = isPublished, + isMainAlias = alias == state.canonicalAlias, + canEditCanonicalAlias = state.actionPermissions.canChangeCanonicalAlias + ) + .show(childFragmentManager, "ROOM_ALIAS_ACTIONS") } - override fun removeLocalAlias(alias: String) { + private fun removeLocalAlias(alias: String) { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_confirmation) .setMessage(getString(R.string.room_alias_delete_confirmation, alias)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index fac8d52159..ff9c023a21 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -157,13 +157,14 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo when (action) { RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm() is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) - is RoomAliasAction.AddAlias -> handleAddAlias() - is RoomAliasAction.RemoveAlias -> handleRemoveAlias(action) + is RoomAliasAction.ManualPublishAlias -> handleAddAlias() + is RoomAliasAction.UnpublishAlias -> handleRemoveAlias(action) is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm() is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + is RoomAliasAction.PublishAlias -> handleAddAliasManually(action) }.exhaustive } @@ -210,22 +211,29 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleAddAlias() = withState { state -> val newAlias = (state.publishManuallyState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState updateCanonicalAlias( - state.canonicalAlias, - state.alternativeAliases + newAlias + canonicalAlias = state.canonicalAlias, + alternativeAliases = state.alternativeAliases + newAlias ) } - private fun handleRemoveAlias(action: RoomAliasAction.RemoveAlias) = withState { state -> + private fun handleAddAliasManually(action: RoomAliasAction.PublishAlias) = withState { state -> updateCanonicalAlias( - state.canonicalAlias, - state.alternativeAliases - action.alias + canonicalAlias = state.canonicalAlias, + alternativeAliases = state.alternativeAliases + action.alias + ) + } + + private fun handleRemoveAlias(action: RoomAliasAction.UnpublishAlias) = withState { state -> + updateCanonicalAlias( + canonicalAlias = state.canonicalAlias, + alternativeAliases = state.alternativeAliases - action.alias ) } private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state -> updateCanonicalAlias( - action.canonicalAlias, - state.alternativeAliases + canonicalAlias = action.canonicalAlias, + alternativeAliases = state.alternativeAliases ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt new file mode 100644 index 0000000000..f9968d86da --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias.detail + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import kotlinx.android.parcel.Parcelize +import javax.inject.Inject + +@Parcelize +data class RoomAliasBottomSheetArgs( + val alias: String, + val isPublished: Boolean, + val isMainAlias: Boolean, + val canEditCanonicalAlias: Boolean +) : Parcelable + +/** + * Bottom sheet fragment that shows room alias information with list of contextual actions + */ +class RoomAliasBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomAliasBottomSheetController.Listener { + + private lateinit var sharedActionViewModel: RoomAliasBottomSheetSharedActionViewModel + @Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool + @Inject lateinit var roomAliasBottomSheetViewModelFactory: RoomAliasBottomSheetViewModel.Factory + @Inject lateinit var controller: RoomAliasBottomSheetController + + private val viewModel: RoomAliasBottomSheetViewModel by fragmentViewModel(RoomAliasBottomSheetViewModel::class) + + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView + + override val showExpanded = true + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getLayoutResId() = R.layout.bottom_sheet_generic_list + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider.get(RoomAliasBottomSheetSharedActionViewModel::class.java) + recyclerView.configureWith(controller, viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun onDestroyView() { + recyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + super.invalidate() + } + + override fun didSelectMenuAction(quickAction: RoomAliasBottomSheetSharedAction) { + sharedActionViewModel.post(quickAction) + + dismiss() + } + + companion object { + fun newInstance(alias: String, + isPublished: Boolean, + isMainAlias: Boolean, + canEditCanonicalAlias: Boolean): RoomAliasBottomSheet { + return RoomAliasBottomSheet().apply { + setArguments(RoomAliasBottomSheetArgs( + alias = alias, + isPublished = isPublished, + isMainAlias = isMainAlias, + canEditCanonicalAlias = canEditCanonicalAlias + )) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt new file mode 100644 index 0000000000..2f4cb357b4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.roomprofile.alias.detail + +import android.view.View +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem +import im.vector.app.core.epoxy.dividerItem +import im.vector.app.core.epoxy.profiles.profileActionItem +import javax.inject.Inject + +/** + * Epoxy controller for room alias actions + */ +class RoomAliasBottomSheetController @Inject constructor() : TypedEpoxyController() { + + var listener: Listener? = null + + override fun buildModels(state: RoomAliasBottomSheetState) { + profileActionItem { + id("alias") + title(state.alias) + subtitle(state.matrixToLink) + } + + // Notifications + dividerItem { + id("aliasSeparator") + } + + var idx = 0 + // Share + state.matrixToLink?.let { + RoomAliasBottomSheetSharedAction.ShareAlias(it).toBottomSheetItem(++idx) + } + + // Action on published alias + if (state.isPublished) { + // Published address + if (state.canEditCanonicalAlias) { + if (state.isMainAlias) { + RoomAliasBottomSheetSharedAction.UnsetMainAlias.toBottomSheetItem(++idx) + } else { + RoomAliasBottomSheetSharedAction.SetMainAlias(state.alias).toBottomSheetItem(++idx) + } + RoomAliasBottomSheetSharedAction.UnPublishAlias(state.alias).toBottomSheetItem(++idx) + } + } else { + // Local address + if (state.canEditCanonicalAlias) { + // Publish + RoomAliasBottomSheetSharedAction.PublishAlias(state.alias).toBottomSheetItem(++idx) + } + // Delete + RoomAliasBottomSheetSharedAction.DeleteAlias(state.alias).toBottomSheetItem(++idx) + } + } + + private fun RoomAliasBottomSheetSharedAction.toBottomSheetItem(index: Int) { + return bottomSheetActionItem { + id("action_$index") + iconRes(iconResId) + textRes(titleRes) + destructive(this@toBottomSheetItem.destructive) + listener(View.OnClickListener { listener?.didSelectMenuAction(this@toBottomSheetItem) }) + } + } + + interface Listener { + fun didSelectMenuAction(quickAction: RoomAliasBottomSheetSharedAction) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt new file mode 100644 index 0000000000..13909c401f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedAction.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias.detail + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import im.vector.app.R +import im.vector.app.core.platform.VectorSharedAction + +sealed class RoomAliasBottomSheetSharedAction( + @StringRes val titleRes: Int, + @DrawableRes val iconResId: Int = 0, + val destructive: Boolean = false) + : VectorSharedAction { + + data class ShareAlias(val matrixTo: String) : RoomAliasBottomSheetSharedAction( + R.string.share, + R.drawable.ic_material_share + ) + + data class PublishAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.room_alias_action_publish + ) + + data class UnPublishAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.room_alias_action_unpublish + ) + + data class DeleteAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.delete, + R.drawable.ic_trash_24, + true + ) + + data class SetMainAlias(val alias: String) : RoomAliasBottomSheetSharedAction( + R.string.room_settings_set_main_address + ) + + object UnsetMainAlias : RoomAliasBottomSheetSharedAction( + R.string.room_settings_unset_main_address + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt new file mode 100644 index 0000000000..5f71783515 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetSharedActionViewModel.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.features.roomprofile.alias.detail + +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +/** + * Activity shared view model to handle room alias quick actions + */ +class RoomAliasBottomSheetSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt new file mode 100644 index 0000000000..97ffcdf30c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.alias.detail + +import com.airbnb.mvrx.MvRxState + +data class RoomAliasBottomSheetState( + val alias: String, + val matrixToLink: String? = null, + val isPublished: Boolean, + val isMainAlias: Boolean, + val canEditCanonicalAlias: Boolean +) : MvRxState { + + constructor(args: RoomAliasBottomSheetArgs) : this( + alias = args.alias, + isPublished = args.isPublished, + isMainAlias = args.isMainAlias, + canEditCanonicalAlias = args.canEditCanonicalAlias + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt new file mode 100644 index 0000000000..7f723cae53 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetViewModel.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.roomprofile.alias.detail + +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import org.matrix.android.sdk.api.session.Session + +class RoomAliasBottomSheetViewModel @AssistedInject constructor( + @Assisted initialState: RoomAliasBottomSheetState, + session: Session +) : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomAliasBottomSheetState): RoomAliasBottomSheetViewModel? { + val fragment: RoomAliasBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.roomAliasBottomSheetViewModelFactory.create(state) + } + } + + init { + setState { + copy( + matrixToLink = session.permalinkService().createPermalink(alias) + ) + } + } + + override fun handle(action: EmptyAction) { + // No op + } +} diff --git a/vector/src/main/res/drawable/ic_trash_24.xml b/vector/src/main/res/drawable/ic_trash_24.xml index 266855d50c..27ad2e29d7 100644 --- a/vector/src/main/res/drawable/ic_trash_24.xml +++ b/vector/src/main/res/drawable/ic_trash_24.xml @@ -1,41 +1,47 @@ - - - - - + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 36f2b38dab..327161233d 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1047,6 +1047,9 @@ This room has no local addresses Add a local address + Publish this address + Unpublish this address + Anyone Members only (since the point in time of selecting this option) From f5ae95d7f16bcdce7ad8b995c7c48fe30f11c5b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 08:34:01 +0100 Subject: [PATCH 191/231] Close form only if necessary --- .../roomprofile/alias/RoomAliasViewModel.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index ff9c023a21..3110c22e21 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -164,7 +164,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) - is RoomAliasAction.PublishAlias -> handleAddAliasManually(action) + is RoomAliasAction.PublishAlias -> handlePublishAlias(action) }.exhaustive } @@ -212,39 +212,45 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo val newAlias = (state.publishManuallyState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState updateCanonicalAlias( canonicalAlias = state.canonicalAlias, - alternativeAliases = state.alternativeAliases + newAlias + alternativeAliases = state.alternativeAliases + newAlias, + closeForm = true ) } - private fun handleAddAliasManually(action: RoomAliasAction.PublishAlias) = withState { state -> + private fun handlePublishAlias(action: RoomAliasAction.PublishAlias) = withState { state -> updateCanonicalAlias( canonicalAlias = state.canonicalAlias, - alternativeAliases = state.alternativeAliases + action.alias + alternativeAliases = state.alternativeAliases + action.alias, + closeForm = false ) } private fun handleRemoveAlias(action: RoomAliasAction.UnpublishAlias) = withState { state -> updateCanonicalAlias( canonicalAlias = state.canonicalAlias, - alternativeAliases = state.alternativeAliases - action.alias - ) + alternativeAliases = state.alternativeAliases - action.alias, + closeForm = false ) } private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state -> updateCanonicalAlias( canonicalAlias = action.canonicalAlias, - alternativeAliases = state.alternativeAliases + alternativeAliases = state.alternativeAliases, + closeForm = false ) } - private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List) { + private fun updateCanonicalAlias(canonicalAlias: String?, alternativeAliases: List, closeForm: Boolean) { postLoading(true) room.updateCanonicalAlias(canonicalAlias, alternativeAliases, object : MatrixCallback { override fun onSuccess(data: Unit) { setState { copy( isLoading = false, - publishManuallyState = RoomAliasViewState.AddAliasState.Closed + publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState, + // Local echo + canonicalAlias = canonicalAlias, + alternativeAliases = alternativeAliases ) } } From 2d4cbde72c906d77ed8d272094b01fb4a286a36a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 08:56:56 +0100 Subject: [PATCH 192/231] Render canonical alias --- .../roomprofile/alias/RoomAliasController.kt | 30 ++++++++++++------- .../roomprofile/alias/RoomAliasFragment.kt | 21 ++++++------- .../roomprofile/alias/RoomAliasViewModel.kt | 16 +++++----- .../detail/RoomAliasBottomSheetController.kt | 1 + vector/src/main/res/values/strings.xml | 3 ++ 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index 6790bee2a8..d3d8a7bed7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -51,7 +51,7 @@ class RoomAliasController @Inject constructor( fun toggleLocalAliasForm() fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() - fun openAlias(alias: String, isPublished: Boolean) + fun openAliasDetail(alias: String, isPublished: Boolean) } var callback: Callback? = null @@ -78,7 +78,17 @@ class RoomAliasController @Inject constructor( helperTextResId(R.string.room_alias_published_alias_subtitle) } - // TODO Set/Unset Canonical + data.canonicalAlias + ?.takeIf { it.isNotEmpty() } + ?.let { canonicalAlias -> + + profileActionItem { + id("canonical") + title(data.canonicalAlias) + subtitle(stringProvider.getString(R.string.room_alias_published_alias_main)) + listener { callback?.openAliasDetail(canonicalAlias, true) } + } + } if (data.alternativeAliases.isEmpty()) { settingsInfoItem { @@ -98,7 +108,7 @@ class RoomAliasController @Inject constructor( profileActionItem { id("alt_$idx") title(altAlias) - listener { callback?.openAlias(altAlias, true) } + listener { callback?.openAliasDetail(altAlias, true) } } } } @@ -110,8 +120,8 @@ class RoomAliasController @Inject constructor( private fun buildPublishManuallyForm(data: RoomAliasViewState) { when (data.publishManuallyState) { - RoomAliasViewState.AddAliasState.Hidden -> Unit - RoomAliasViewState.AddAliasState.Closed -> { + RoomAliasViewState.AddAliasState.Hidden -> Unit + RoomAliasViewState.AddAliasState.Closed -> { settingsButtonItem { id("publishManually") colorProvider(colorProvider) @@ -154,16 +164,16 @@ class RoomAliasController @Inject constructor( id("loadingAliases") } } - is Success -> { + is Success -> { localAliases().forEachIndexed { idx, localAlias -> profileActionItem { id("loc_$idx") title(localAlias) - listener { callback?.openAlias(localAlias, false) } + listener { callback?.openAliasDetail(localAlias, false) } } } } - is Fail -> { + is Fail -> { errorWithRetryItem { id("alt_error") text(errorFormatter.toHumanReadable(localAliases.error)) @@ -177,8 +187,8 @@ class RoomAliasController @Inject constructor( private fun buildAddLocalAlias(data: RoomAliasViewState) { when (data.newLocalAliasState) { - RoomAliasViewState.AddAliasState.Hidden -> Unit - RoomAliasViewState.AddAliasState.Closed -> { + RoomAliasViewState.AddAliasState.Hidden -> Unit + RoomAliasViewState.AddAliasState.Closed -> { settingsButtonItem { id("newLocalAliasButton") colorProvider(colorProvider) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index f54cff5c6c..9d3b7feda6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -113,29 +113,26 @@ class RoomAliasFragment @Inject constructor( super.onDestroyView() } - override fun invalidate() = withState(viewModel) { viewState -> - controller.setData(viewState) - renderRoomSummary(viewState) + override fun invalidate() = withState(viewModel) { state -> + waiting_view.isVisible = state.isLoading + controller.setData(state) + renderRoomSummary(state) } private fun renderRoomSummary(state: RoomAliasViewState) { - waiting_view.isVisible = state.isLoading - state.roomSummary()?.let { roomSettingsToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), roomSettingsToolbarAvatarImageView) } - - invalidateOptionsMenu() } - private fun unpublishAlias(altAlias: String) { + private fun unpublishAlias(alias: String) { AlertDialog.Builder(requireContext()) .setTitle(R.string.dialog_title_confirmation) - .setMessage(getString(R.string.room_alias_delete_confirmation, altAlias)) + .setMessage(getString(R.string.room_alias_unpublish_confirmation, alias)) .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.delete) { _, _ -> - viewModel.handle(RoomAliasAction.UnpublishAlias(altAlias)) + .setPositiveButton(R.string.action_unpublish) { _, _ -> + viewModel.handle(RoomAliasAction.UnpublishAlias(alias)) } .show() .withColoredButton(DialogInterface.BUTTON_POSITIVE) @@ -165,7 +162,7 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.AddLocalAlias) } - override fun openAlias(alias: String, isPublished: Boolean) = withState(viewModel) { state -> + override fun openAliasDetail(alias: String, isPublished: Boolean) = withState(viewModel) { state -> RoomAliasBottomSheet .newInstance( alias = alias, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 3110c22e21..8cfd589bb3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -157,8 +157,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo when (action) { RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm() is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) - is RoomAliasAction.ManualPublishAlias -> handleAddAlias() - is RoomAliasAction.UnpublishAlias -> handleRemoveAlias(action) + is RoomAliasAction.ManualPublishAlias -> handleManualPublishAlias() + is RoomAliasAction.UnpublishAlias -> handleUnpublishAlias(action) is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm() is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) @@ -208,7 +208,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo } } - private fun handleAddAlias() = withState { state -> + private fun handleManualPublishAlias() = withState { state -> val newAlias = (state.publishManuallyState as? RoomAliasViewState.AddAliasState.Editing)?.value ?: return@withState updateCanonicalAlias( canonicalAlias = state.canonicalAlias, @@ -225,7 +225,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo ) } - private fun handleRemoveAlias(action: RoomAliasAction.UnpublishAlias) = withState { state -> + private fun handleUnpublishAlias(action: RoomAliasAction.UnpublishAlias) = withState { state -> updateCanonicalAlias( canonicalAlias = state.canonicalAlias, alternativeAliases = state.alternativeAliases - action.alias, @@ -235,7 +235,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state -> updateCanonicalAlias( canonicalAlias = action.canonicalAlias, - alternativeAliases = state.alternativeAliases, + // Ensure the previous canonical alias is moved to the alt aliases + alternativeAliases = (state.alternativeAliases + listOfNotNull(state.canonicalAlias)).distinct(), closeForm = false ) } @@ -247,10 +248,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo setState { copy( isLoading = false, - publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState, - // Local echo - canonicalAlias = canonicalAlias, - alternativeAliases = alternativeAliases + publishManuallyState = if (closeForm) RoomAliasViewState.AddAliasState.Closed else publishManuallyState ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt index 2f4cb357b4..56a93d1527 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt @@ -34,6 +34,7 @@ class RoomAliasBottomSheetController @Inject constructor() : TypedEpoxyControlle id("alias") title(state.alias) subtitle(state.matrixToLink) + editable(false) } // Notifications diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 327161233d..6e55567428 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -138,6 +138,7 @@ Close Copy Add + Unpublish Copied to clipboard Disable @@ -1030,10 +1031,12 @@ Room Addresses Published Addresses Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first. + This is the main address Main address Other published addresses: Published a new address manually Publish + Unpublish the address \"%1$s\"? Delete the address \"%1$s\"? Publish this room to the public in %1$s\'s room directory? From a570528f6c6e2f693c24fa050e6198c2cb1b979c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 09:26:02 +0100 Subject: [PATCH 193/231] More fixes --- .../app/features/roomprofile/alias/RoomAliasController.kt | 8 ++++---- .../app/features/roomprofile/alias/RoomAliasFragment.kt | 5 +++-- .../app/features/roomprofile/alias/RoomAliasViewModel.kt | 2 +- .../app/features/roomprofile/alias/RoomAliasViewState.kt | 3 +++ .../roomprofile/alias/detail/RoomAliasBottomSheet.kt | 3 +++ .../alias/detail/RoomAliasBottomSheetController.kt | 6 ++++-- .../roomprofile/alias/detail/RoomAliasBottomSheetState.kt | 2 ++ vector/src/main/res/values/strings.xml | 2 +- 8 files changed, 21 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index d3d8a7bed7..56f3cb77c5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -51,7 +51,7 @@ class RoomAliasController @Inject constructor( fun toggleLocalAliasForm() fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() - fun openAliasDetail(alias: String, isPublished: Boolean) + fun openAliasDetail(alias: String) } var callback: Callback? = null @@ -86,7 +86,7 @@ class RoomAliasController @Inject constructor( id("canonical") title(data.canonicalAlias) subtitle(stringProvider.getString(R.string.room_alias_published_alias_main)) - listener { callback?.openAliasDetail(canonicalAlias, true) } + listener { callback?.openAliasDetail(canonicalAlias) } } } @@ -108,7 +108,7 @@ class RoomAliasController @Inject constructor( profileActionItem { id("alt_$idx") title(altAlias) - listener { callback?.openAliasDetail(altAlias, true) } + listener { callback?.openAliasDetail(altAlias) } } } } @@ -169,7 +169,7 @@ class RoomAliasController @Inject constructor( profileActionItem { id("loc_$idx") title(localAlias) - listener { callback?.openAliasDetail(localAlias, false) } + listener { callback?.openAliasDetail(localAlias) } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 9d3b7feda6..dd02691259 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -162,12 +162,13 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.AddLocalAlias) } - override fun openAliasDetail(alias: String, isPublished: Boolean) = withState(viewModel) { state -> + override fun openAliasDetail(alias: String) = withState(viewModel) { state -> RoomAliasBottomSheet .newInstance( alias = alias, - isPublished = isPublished, + isPublished = alias in state.allPublishedAliases, isMainAlias = alias == state.canonicalAlias, + isLocal = alias in state.localAliases().orEmpty(), canEditCanonicalAlias = state.actionPermissions.canChangeCanonicalAlias ) .show(childFragmentManager, "ROOM_ALIAS_ACTIONS") diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 8cfd589bb3..4aed4a55bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -236,7 +236,7 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo updateCanonicalAlias( canonicalAlias = action.canonicalAlias, // Ensure the previous canonical alias is moved to the alt aliases - alternativeAliases = (state.alternativeAliases + listOfNotNull(state.canonicalAlias)).distinct(), + alternativeAliases = state.allPublishedAliases, closeForm = false ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index e1736296c6..cffff2ec4f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -37,6 +37,9 @@ data class RoomAliasViewState( constructor(args: RoomProfileArgs) : this(roomId = args.roomId) + val allPublishedAliases: List + get() = (alternativeAliases + listOfNotNull(canonicalAlias)).distinct() + data class ActionPermissions( val canChangeCanonicalAlias: Boolean = false ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt index f9968d86da..86702d1507 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheet.kt @@ -36,6 +36,7 @@ data class RoomAliasBottomSheetArgs( val alias: String, val isPublished: Boolean, val isMainAlias: Boolean, + val isLocal: Boolean, val canEditCanonicalAlias: Boolean ) : Parcelable @@ -90,12 +91,14 @@ class RoomAliasBottomSheet : VectorBaseBottomSheetDialogFragment(), RoomAliasBot fun newInstance(alias: String, isPublished: Boolean, isMainAlias: Boolean, + isLocal: Boolean, canEditCanonicalAlias: Boolean): RoomAliasBottomSheet { return RoomAliasBottomSheet().apply { setArguments(RoomAliasBottomSheetArgs( alias = alias, isPublished = isPublished, isMainAlias = isMainAlias, + isLocal = isLocal, canEditCanonicalAlias = canEditCanonicalAlias )) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt index 56a93d1527..157037c13d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetController.kt @@ -59,9 +59,11 @@ class RoomAliasBottomSheetController @Inject constructor() : TypedEpoxyControlle } RoomAliasBottomSheetSharedAction.UnPublishAlias(state.alias).toBottomSheetItem(++idx) } - } else { + } + + if (state.isLocal) { // Local address - if (state.canEditCanonicalAlias) { + if (state.canEditCanonicalAlias && state.isPublished.not()) { // Publish RoomAliasBottomSheetSharedAction.PublishAlias(state.alias).toBottomSheetItem(++idx) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt index 97ffcdf30c..a61075cef6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/detail/RoomAliasBottomSheetState.kt @@ -23,6 +23,7 @@ data class RoomAliasBottomSheetState( val matrixToLink: String? = null, val isPublished: Boolean, val isMainAlias: Boolean, + val isLocal: Boolean, val canEditCanonicalAlias: Boolean ) : MvRxState { @@ -30,6 +31,7 @@ data class RoomAliasBottomSheetState( alias = args.alias, isPublished = args.isPublished, isMainAlias = args.isMainAlias, + isLocal = args.isLocal, canEditCanonicalAlias = args.canEditCanonicalAlias ) } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 6e55567428..d9df53d99f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1034,7 +1034,7 @@ This is the main address Main address Other published addresses: - Published a new address manually + Publish a new address manually Publish Unpublish the address \"%1$s\"? Delete the address \"%1$s\"? From 90e0006caef776fcf657fe2121635d24cc3aa55c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 11:01:07 +0100 Subject: [PATCH 194/231] Room directory visibility management --- CHANGES.md | 1 + .../api/session/room/RoomDirectoryService.kt | 11 +++ .../session/directory/DirectoryAPI.kt | 18 +++++ .../directory/RoomDirectoryVisibilityJson.kt | 29 +++++++ .../room/DefaultRoomDirectoryService.kt | 20 ++++- .../sdk/internal/session/room/RoomModule.kt | 10 +++ .../GetRoomDirectoryVisibilityTask.kt | 44 +++++++++++ .../SetRoomDirectoryVisibilityTask.kt | 47 ++++++++++++ .../app/features/form/FormSwitchItem.kt | 2 +- .../roomprofile/alias/RoomAliasAction.kt | 4 + .../roomprofile/alias/RoomAliasController.kt | 38 +++++++++- .../roomprofile/alias/RoomAliasFragment.kt | 5 ++ .../roomprofile/alias/RoomAliasViewModel.kt | 76 ++++++++++++++++--- .../roomprofile/alias/RoomAliasViewState.kt | 2 + vector/src/main/res/values/strings.xml | 7 +- 15 files changed, 296 insertions(+), 18 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt diff --git a/CHANGES.md b/CHANGES.md index e48281081b..30713deb18 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ Features ✨: - Create DMs with users by scanning their QR code (#2025) - Add Invite friends quick invite actions (#2348) - Add friend by scanning QR code, show your code to friends (#2025) + - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) Improvements 🙌: - New room creation tile with quick action (#2346) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt index dc5b3d55f5..61970ce848 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol @@ -39,4 +40,14 @@ interface RoomDirectoryService { * Includes both the available protocols and all fields required for queries against each protocol. */ fun getThirdPartyProtocol(callback: MatrixCallback>): Cancelable + + /** + * Get the visibility of a room in the directory + */ + suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility + + /** + * Set the visibility of a room in the directory + */ + suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 3eff4b05aa..d0de5f2397 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -35,6 +35,24 @@ internal interface DirectoryAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call + /** + * Get the room directory visibility. + * + * @param roomId the room id. + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") + fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): Call + + /** + * Set the room directory visibility. + * + * @param roomId the room id. + * @param body the body containing the new directory visibility + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") + fun setRoomDirectoryVisibility(@Path("roomId") roomId: String, + @Body body: RoomDirectoryVisibilityJson): Call + /** * Add alias to the room. * @param roomAlias the room alias. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt new file mode 100644 index 0000000000..ddf927a3dc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/RoomDirectoryVisibilityJson.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.directory + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility + +@JsonClass(generateAdapter = true) +internal data class RoomDirectoryVisibilityJson( + /** + * The visibility of the room in the directory. One of: ["private", "public"] + */ + @Json(name = "visibility") val visibility: RoomDirectoryVisibility +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt index a091b5f85e..0d41c6f35e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt @@ -18,19 +18,25 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.RoomDirectoryService +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask +import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask +import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import javax.inject.Inject -internal class DefaultRoomDirectoryService @Inject constructor(private val getPublicRoomTask: GetPublicRoomTask, - private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask, - private val taskExecutor: TaskExecutor) : RoomDirectoryService { +internal class DefaultRoomDirectoryService @Inject constructor( + private val getPublicRoomTask: GetPublicRoomTask, + private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask, + private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask, + private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask, + private val taskExecutor: TaskExecutor) : RoomDirectoryService { override fun getPublicRooms(server: String?, publicRoomsParams: PublicRoomsParams, @@ -49,4 +55,12 @@ internal class DefaultRoomDirectoryService @Inject constructor(private val getPu } .executeBy(taskExecutor) } + + override suspend fun getRoomDirectoryVisibility(roomId: String): RoomDirectoryVisibility { + return getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId)) + } + + override suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) { + setRoomDirectoryVisibilityTask.execute(SetRoomDirectoryVisibilityTask.Params(roomId, roomDirectoryVisibility)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 8ad82e0e15..3a94396a61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -38,9 +38,13 @@ import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTas import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask +import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetThirdPartyProtocolsTask +import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask +import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.GetThirdPartyProtocolsTask +import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.admin.DefaultMembershipAdminTask @@ -139,6 +143,12 @@ internal abstract class RoomModule { @Binds abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask + @Binds + abstract fun bindGetRoomDirectoryVisibilityTask(task: DefaultGetRoomDirectoryVisibilityTask): GetRoomDirectoryVisibilityTask + + @Binds + abstract fun bindSetRoomDirectoryVisibilityTask(task: DefaultSetRoomDirectoryVisibilityTask): SetRoomDirectoryVisibilityTask + @Binds abstract fun bindGetThirdPartyProtocolsTask(task: DefaultGetThirdPartyProtocolsTask): GetThirdPartyProtocolsTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt new file mode 100644 index 0000000000..fbdd6a03eb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.directory + +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetRoomDirectoryVisibilityTask : Task { + data class Params( + val roomId: String + ) +} + +internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor( + private val directoryAPI: DirectoryAPI, + private val eventBus: EventBus +) : GetRoomDirectoryVisibilityTask { + + override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility { + return executeRequest(eventBus) { + apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId) + } + .visibility + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt new file mode 100644 index 0000000000..33b12aa1ca --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.directory + +import org.greenrobot.eventbus.EventBus +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface SetRoomDirectoryVisibilityTask : Task { + data class Params( + val roomId: String, + val roomDirectoryVisibility: RoomDirectoryVisibility + ) +} + +internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor( + private val directoryAPI: DirectoryAPI, + private val eventBus: EventBus +) : SetRoomDirectoryVisibilityTask { + + override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) { + executeRequest(eventBus) { + apiCall = directoryAPI.setRoomDirectoryVisibility( + params.roomId, + RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility) + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt b/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt index 0b274cccd8..ff4f8711e0 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormSwitchItem.kt @@ -61,8 +61,8 @@ abstract class FormSwitchItem : VectorEpoxyModel() { holder.switchView.isEnabled = enabled + holder.switchView.setOnCheckedChangeListener(null) holder.switchView.isChecked = switchChecked - holder.switchView.setOnCheckedChangeListener { _, isChecked -> listener?.invoke(isChecked) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt index 4054d6f63a..80e1603453 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasAction.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.alias import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility sealed class RoomAliasAction : VectorViewModelAction { // Canonical @@ -27,6 +28,9 @@ sealed class RoomAliasAction : VectorViewModelAction { data class UnpublishAlias(val alias: String) : RoomAliasAction() data class SetCanonicalAlias(val canonicalAlias: String?) : RoomAliasAction() + // Room directory + data class SetRoomDirectoryVisibility(val roomDirectoryVisibility: RoomDirectoryVisibility) : RoomAliasAction() + // Local data class RemoveLocalAlias(val alias: String) : RoomAliasAction() object ToggleAddLocalAliasForm : RoomAliasAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index 56f3cb77c5..64caa1e525 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile.alias import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import im.vector.app.R @@ -32,9 +33,11 @@ import im.vector.app.features.discovery.settingsButtonItem import im.vector.app.features.discovery.settingsContinueCancelItem import im.vector.app.features.discovery.settingsInfoItem import im.vector.app.features.form.formEditTextItem +import im.vector.app.features.form.formSwitchItem import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter import im.vector.app.features.roomdirectory.createroom.roomAliasEditItem import org.matrix.android.sdk.api.session.room.alias.RoomAliasError +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import javax.inject.Inject class RoomAliasController @Inject constructor( @@ -48,6 +51,7 @@ class RoomAliasController @Inject constructor( fun toggleManualPublishForm() fun setNewAlias(value: String) fun addAlias() + fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility) fun toggleLocalAliasForm() fun setNewLocalAliasLocalPart(value: String) fun addLocalAlias() @@ -63,12 +67,42 @@ class RoomAliasController @Inject constructor( override fun buildModels(data: RoomAliasViewState?) { data ?: return - // Published + // Published alias buildPublishInfo(data) - // Local + // Room directory visibility + buildRoomDirectoryVisibility(data) + // Local alias buildLocalInfo(data) } + private fun buildRoomDirectoryVisibility(data: RoomAliasViewState) { + when (data.roomDirectoryVisibility) { + Uninitialized -> Unit + is Loading -> Unit + is Success -> { + formSwitchItem { + id("roomVisibility") + title(stringProvider.getString(R.string.room_alias_publish_to_directory, data.homeServerName)) + showDivider(false) + switchChecked(data.roomDirectoryVisibility() == RoomDirectoryVisibility.PUBLIC) + listener { + if (it) { + callback?.setRoomDirectoryVisibility(RoomDirectoryVisibility.PUBLIC) + } else { + callback?.setRoomDirectoryVisibility(RoomDirectoryVisibility.PRIVATE) + } + } + } + } + is Fail -> { + errorWithRetryItem { + text(stringProvider.getString(R.string.room_alias_publish_to_directory_error, + errorFormatter.toHumanReadable(data.roomDirectoryVisibility.error))) + } + } + } + } + private fun buildPublishInfo(data: RoomAliasViewState) { buildProfileSection( stringProvider.getString(R.string.room_alias_published_alias_title) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index dd02691259..916b40970e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -40,6 +40,7 @@ import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetShare import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.matrix.android.sdk.api.session.room.alias.RoomAliasError +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -150,6 +151,10 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.ManualPublishAlias) } + override fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility) { + viewModel.handle(RoomAliasAction.SetRoomDirectoryVisibility(roomDirectoryVisibility)) + } + override fun toggleLocalAliasForm() { viewModel.handle(RoomAliasAction.ToggleAddLocalAliasForm) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 4aed4a55bb..58aee5f084 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -67,6 +67,35 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo observePowerLevel() observeRoomCanonicalAlias() fetchRoomAlias() + fetchRoomDirectoryVisibility() + } + + private fun fetchRoomDirectoryVisibility() { + setState { + copy( + roomDirectoryVisibility = Loading() + ) + } + viewModelScope.launch { + runCatching { + session.getRoomDirectoryVisibility(room.roomId) + }.fold( + { + setState { + copy( + roomDirectoryVisibility = Success(it) + ) + } + }, + { + setState { + copy( + roomDirectoryVisibility = Fail(it) + ) + } + } + ) + } } private fun initHomeServerName() { @@ -155,19 +184,43 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo override fun handle(action: RoomAliasAction) { when (action) { - RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm() - is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) - is RoomAliasAction.ManualPublishAlias -> handleManualPublishAlias() - is RoomAliasAction.UnpublishAlias -> handleUnpublishAlias(action) - is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) - RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm() - is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) - RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() - is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) - is RoomAliasAction.PublishAlias -> handlePublishAlias(action) + RoomAliasAction.ToggleManualPublishForm -> handleToggleManualPublishForm() + is RoomAliasAction.SetNewAlias -> handleSetNewAlias(action) + is RoomAliasAction.ManualPublishAlias -> handleManualPublishAlias() + is RoomAliasAction.UnpublishAlias -> handleUnpublishAlias(action) + is RoomAliasAction.SetCanonicalAlias -> handleSetCanonicalAlias(action) + is RoomAliasAction.SetRoomDirectoryVisibility -> handleSetRoomDirectoryVisibility(action) + RoomAliasAction.ToggleAddLocalAliasForm -> handleToggleAddLocalAliasForm() + is RoomAliasAction.SetNewLocalAliasLocalPart -> handleSetNewLocalAliasLocalPart(action) + RoomAliasAction.AddLocalAlias -> handleAddLocalAlias() + is RoomAliasAction.RemoveLocalAlias -> handleRemoveLocalAlias(action) + is RoomAliasAction.PublishAlias -> handlePublishAlias(action) }.exhaustive } + private fun handleSetRoomDirectoryVisibility(action: RoomAliasAction.SetRoomDirectoryVisibility) { + postLoading(true) + viewModelScope.launch { + runCatching { + session.setRoomDirectoryVisibility(room.roomId, action.roomDirectoryVisibility) + }.fold( + { + setState { + copy( + isLoading = false, + // Local echo, no need to fetch the data from the server again + roomDirectoryVisibility = Success(action.roomDirectoryVisibility) + ) + } + }, + { + postLoading(false) + _viewEvents.post(RoomAliasViewEvents.Failure(it)) + } + ) + } + } + private fun handleToggleAddLocalAliasForm() { setState { copy( @@ -229,7 +282,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo updateCanonicalAlias( canonicalAlias = state.canonicalAlias, alternativeAliases = state.alternativeAliases - action.alias, - closeForm = false ) + closeForm = false + ) } private fun handleSetCanonicalAlias(action: RoomAliasAction.SetCanonicalAlias) = withState { state -> diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt index cffff2ec4f..f6341f4f64 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewState.kt @@ -20,6 +20,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomSummary data class RoomAliasViewState( @@ -27,6 +28,7 @@ data class RoomAliasViewState( val homeServerName: String = "", val roomSummary: Async = Uninitialized, val actionPermissions: ActionPermissions = ActionPermissions(), + val roomDirectoryVisibility: Async = Uninitialized, val isLoading: Boolean = false, val canonicalAlias: String? = null, val alternativeAliases: List = emptyList(), diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index d9df53d99f..5e15b3f429 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1026,7 +1026,7 @@ Room addresses - See and managed addresses of this room + See and managed addresses of this room, and its visibility in the room directory. Room Addresses Published Addresses @@ -1053,6 +1053,11 @@ Publish this address Unpublish this address + + Publish this room to the public in %1$s\'s room directory? + + Unable to retrieve the current room directory visibility (%1$s). + Anyone Members only (since the point in time of selecting this option) From 0da0857970d2dd50bc7f33c23922ebe83d20b761 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 11:14:48 +0100 Subject: [PATCH 195/231] Cleanup --- .../android/sdk/api/session/room/alias/RoomAliasError.kt | 2 +- .../session/room/alias/RoomAliasAvailabilityChecker.kt | 2 +- .../session/room/membership/RoomDisplayNameResolver.kt | 1 - .../im/vector/app/core/epoxy/profiles/ProfileActionItem.kt | 1 - .../roomdirectory/createroom/CreateRoomController.kt | 6 ++++-- .../app/features/roomprofile/alias/RoomAliasViewModel.kt | 7 +++++-- 6 files changed, 11 insertions(+), 8 deletions(-) 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 1dbdd9cced..d2cb7c58a9 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 @@ -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/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index 0abb158521..d47450aaee 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 @@ -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/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index f744af94c6..784b610af7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.R import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.internal.database.mapper.ContentMapper diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt index f52051f989..2769121afa 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileActionItem.kt @@ -17,7 +17,6 @@ package im.vector.app.core.epoxy.profiles import android.content.res.ColorStateList -import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat 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 de9ecb2825..94b419797d 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt @@ -27,7 +27,6 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.form.formSubmitButtonItem import im.vector.app.features.form.formSwitchItem -import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import javax.inject.Inject @@ -104,7 +103,10 @@ class CreateRoomController @Inject constructor( enabled(enableFormElement) value(viewState.roomType.aliasLocalPart) homeServer(":" + viewState.homeServerName) - errorMessage(roomAliasErrorFormatter.format((((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError)) + errorMessage( + roomAliasErrorFormatter.format( + (((viewState.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError) + ) onTextChange { value -> listener?.setAliasLocalPart(value) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 58aee5f084..66daf6e5db 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -142,8 +142,11 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo .subscribe { val powerLevelsHelper = PowerLevelsHelper(it) val permissions = RoomAliasViewState.ActionPermissions( - canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_CANONICAL_ALIAS), + canChangeCanonicalAlias = powerLevelsHelper.isUserAllowedToSend( + userId = session.myUserId, + isState = true, + eventType = EventType.STATE_ROOM_CANONICAL_ALIAS + ) ) setState { val newPublishManuallyState = if (permissions.canChangeCanonicalAlias) { From 412fc78c9af90c69c3088f5cf9e18602d36b6029 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 11:27:36 +0100 Subject: [PATCH 196/231] Cleanup --- .../android/sdk/internal/session/directory/DirectoryAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index d0de5f2397..6a50f3ee37 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -62,7 +62,7 @@ internal interface DirectoryAPI { @Body body: AddRoomAliasBody): Call /** - * Delete a room aliases + * Delete a room alias * @param roomAlias the room alias. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") From 6c7eb6ea8c7407d6ca6d4aac17e06590fc168b4a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 15:45:45 +0100 Subject: [PATCH 197/231] Small wording change --- vector/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 5e15b3f429..d642f84d65 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1104,8 +1104,8 @@ You will have no main address specified for this room." Main address warnings - Set as Main Address - Unset as Main Address + Set as main address + Unset as main address Copy Room ID Copy Room Address From 50ddd3cf3118e8d91ec9d8adf2569b2d1a7b11ff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Nov 2020 16:14:38 +0100 Subject: [PATCH 198/231] Small cleanup, handle case of no local alias, handle unpublish of canonical alias --- .../sdk/internal/session/room/RoomAPI.kt | 3 ++- .../session/room/alias/GetAliasesResponse.kt | 2 +- .../roomprofile/alias/RoomAliasController.kt | 17 ++++++++++++----- .../roomprofile/alias/RoomAliasViewModel.kt | 3 ++- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 44f52f5f3a..955a251b52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -320,7 +320,8 @@ internal interface RoomAPI { @Body body: ReportContentBody): Call /** - * Get local aliases of this room + * Get a list of aliases maintained by the local server for the given room. + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases") fun getAliases(@Path("roomId") roomId: String): Call diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt index 499abf33aa..5965924085 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetAliasesResponse.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class GetAliasesResponse( /** - * The list of aliases currently defined on the local server for the given room + * Required. The server's local aliases on the room. Can be empty. */ @Json(name = "aliases") val aliases: List = emptyList() ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index 64caa1e525..e1c8a3803e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -199,11 +199,18 @@ class RoomAliasController @Inject constructor( } } is Success -> { - localAliases().forEachIndexed { idx, localAlias -> - profileActionItem { - id("loc_$idx") - title(localAlias) - listener { callback?.openAliasDetail(localAlias) } + if (localAliases().isEmpty()) { + settingsInfoItem { + id("locEmpty") + helperTextResId(R.string.room_alias_local_address_empty) + } + } else { + localAliases().forEachIndexed { idx, localAlias -> + profileActionItem { + id("loc_$idx") + title(localAlias) + listener { callback?.openAliasDetail(localAlias) } + } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt index 66daf6e5db..5873d9ce8a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasViewModel.kt @@ -283,7 +283,8 @@ class RoomAliasViewModel @AssistedInject constructor(@Assisted initialState: Roo private fun handleUnpublishAlias(action: RoomAliasAction.UnpublishAlias) = withState { state -> updateCanonicalAlias( - canonicalAlias = state.canonicalAlias, + // We can also unpublish the canonical alias + canonicalAlias = state.canonicalAlias.takeIf { it != action.alias }, alternativeAliases = state.alternativeAliases - action.alias, closeForm = false ) From 9c53f0f88141163e128e5a6c88f6c9d20c04fe29 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Nov 2020 17:41:39 +0100 Subject: [PATCH 199/231] Clarify aliasLocalPart --- .../android/sdk/api/session/room/alias/AliasService.kt | 3 +++ .../sdk/internal/session/room/alias/AddRoomAliasTask.kt | 8 ++++++-- .../session/room/alias/RoomAliasAvailabilityChecker.kt | 8 ++++++-- .../app/features/roomprofile/alias/RoomAliasController.kt | 4 ++-- .../app/features/roomprofile/alias/RoomAliasFragment.kt | 8 ++++---- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt index 3060824f0f..5fe7e99425 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/AliasService.kt @@ -19,11 +19,14 @@ package org.matrix.android.sdk.api.session.room.alias interface AliasService { /** * Get list of local alias of the room + * @return the list of the aliases (full aliases, not only the local part) */ suspend fun getRoomAliases(): List /** * Add local alias to the room + * @param aliasLocalPart the local part of the alias. + * Ex: for the alias "#my_alias:example.org", the local part is "my_alias" */ suspend fun addAlias(aliasLocalPart: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt index 4dad476f03..9793750fa0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt @@ -20,13 +20,17 @@ import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI -import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker.Companion.toFullAlias +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker.Companion.toFullLocalAlias import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject internal interface AddRoomAliasTask : Task { data class Params( val roomId: String, + /** + * the local part of the alias. + * Ex: for the alias "#my_alias:example.org", the local part is "my_alias" + */ val aliasLocalPart: String ) } @@ -43,7 +47,7 @@ internal class DefaultAddRoomAliasTask @Inject constructor( executeRequest(eventBus) { apiCall = directoryAPI.addRoomAlias( - roomAlias = params.aliasLocalPart.toFullAlias(userId), + roomAlias = params.aliasLocalPart.toFullLocalAlias(userId), body = AddRoomAliasBody( roomId = params.roomId ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index d47450aaee..25ba493891 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 @@ -29,13 +29,17 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( private val directoryAPI: DirectoryAPI, private val eventBus: EventBus ) { + /** + * @param aliasLocalPart the local part of the alias. + * Ex: for the alias "#my_alias:example.org", the local part is "my_alias" + */ @Throws(RoomAliasError::class) suspend fun check(aliasLocalPart: String?) { if (aliasLocalPart.isNullOrEmpty()) { throw RoomAliasError.AliasEmpty } // Check alias availability - val fullAlias = aliasLocalPart.toFullAlias(userId) + val fullAlias = aliasLocalPart.toFullLocalAlias(userId) try { executeRequest(eventBus) { apiCall = directoryAPI.getRoomIdByAlias(fullAlias) @@ -56,6 +60,6 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( } companion object { - internal fun String.toFullAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":") + internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":") } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index e1c8a3803e..49bccd79db 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -49,11 +49,11 @@ class RoomAliasController @Inject constructor( interface Callback { fun toggleManualPublishForm() - fun setNewAlias(value: String) + fun setNewAlias(alias: String) fun addAlias() fun setRoomDirectoryVisibility(roomDirectoryVisibility: RoomDirectoryVisibility) fun toggleLocalAliasForm() - fun setNewLocalAliasLocalPart(value: String) + fun setNewLocalAliasLocalPart(aliasLocalPart: String) fun addLocalAlias() fun openAliasDetail(alias: String) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 916b40970e..56c3e76828 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -143,8 +143,8 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.ToggleManualPublishForm) } - override fun setNewAlias(value: String) { - viewModel.handle(RoomAliasAction.SetNewAlias(value)) + override fun setNewAlias(alias: String) { + viewModel.handle(RoomAliasAction.SetNewAlias(alias)) } override fun addAlias() { @@ -159,8 +159,8 @@ class RoomAliasFragment @Inject constructor( viewModel.handle(RoomAliasAction.ToggleAddLocalAliasForm) } - override fun setNewLocalAliasLocalPart(value: String) { - viewModel.handle(RoomAliasAction.SetNewLocalAliasLocalPart(value)) + override fun setNewLocalAliasLocalPart(aliasLocalPart: String) { + viewModel.handle(RoomAliasAction.SetNewLocalAliasLocalPart(aliasLocalPart)) } override fun addLocalAlias() { From 0cfea40b2477ec4ad92f5f12a097d743dfd6e3e1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Nov 2020 17:57:42 +0100 Subject: [PATCH 200/231] Use when statement and modify the case to simplify the code --- .../timeline/format/NoticeEventFormatter.kt | 72 +++++++++---------- 1 file changed, 34 insertions(+), 38 deletions(-) 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 0db6a374e9..b67a82b105 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 @@ -473,67 +473,63 @@ class NoticeEventFormatter @Inject constructor( val added = altAliases - prevAltAliases val removed = prevAltAliases - altAliases - return if (added.isEmpty() && removed.isEmpty() && canonicalAlias == prevCanonicalAlias) { - // in case there is no difference between the two events say something as we can't simply hide the event from here - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_canonical_alias_no_change_by_you) - } else { - sp.getString(R.string.notice_room_canonical_alias_no_change, senderName) - } - } else if (added.isEmpty() && removed.isEmpty()) { - // Canonical has changed - if (canonicalAlias != null) { + return when { + added.isEmpty() && removed.isEmpty() && canonicalAlias == prevCanonicalAlias -> { + // No difference between the two events say something as we can't simply hide the event from here if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_canonical_alias_set_by_you, canonicalAlias) + sp.getString(R.string.notice_room_canonical_alias_no_change_by_you) } else { - sp.getString(R.string.notice_room_canonical_alias_set, senderName, canonicalAlias) - } - } else { - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_canonical_alias_unset_by_you) - } else { - sp.getString(R.string.notice_room_canonical_alias_unset, senderName) + sp.getString(R.string.notice_room_canonical_alias_no_change, senderName) } } - } else if (added.isEmpty()) { - if (canonicalAlias == prevCanonicalAlias) { + added.isEmpty() && removed.isEmpty() -> { + // Canonical has changed + if (canonicalAlias != null) { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_set_by_you, canonicalAlias) + } else { + sp.getString(R.string.notice_room_canonical_alias_set, senderName, canonicalAlias) + } + } else { + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_unset_by_you) + } else { + sp.getString(R.string.notice_room_canonical_alias_unset, senderName) + } + } + } + added.isEmpty() && canonicalAlias == prevCanonicalAlias -> { // Some alternative has been removed if (event.isSentByCurrentUser()) { sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed_by_you, removed.size, removed.joinToString()) } else { sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_removed, removed.size, senderName, removed.joinToString()) } - } else { - // Main and removed - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed_by_you) - } else { - sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed, senderName) - } } - } else if (removed.isEmpty()) { - if (canonicalAlias == prevCanonicalAlias) { + removed.isEmpty() && canonicalAlias == prevCanonicalAlias -> { // Some alternative has been added if (event.isSentByCurrentUser()) { sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added_by_you, added.size, added.joinToString()) } else { sp.getQuantityString(R.plurals.notice_room_canonical_alias_alternative_added, added.size, senderName, added.joinToString()) } - } else { - // Main and added + } + canonicalAlias == prevCanonicalAlias -> { + // Alternative added and removed + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_canonical_alias_alternative_changed_by_you) + } else { + sp.getString(R.string.notice_room_canonical_alias_alternative_changed, senderName) + } + } + else -> { + // Main and removed, or main and added, or main and added and removed if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed_by_you) } else { sp.getString(R.string.notice_room_canonical_alias_main_and_alternative_changed, senderName) } } - } else { - // Alternative added and removed - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_canonical_alias_alternative_changed_by_you) - } else { - sp.getString(R.string.notice_room_canonical_alias_alternative_changed, senderName) - } } } From c0295645902dde3dc3567aacacadc1972505396a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Nov 2020 18:03:38 +0100 Subject: [PATCH 201/231] Rename method and class for more clarity --- .../app/features/roomprofile/RoomProfileActivity.kt | 10 +++++----- .../features/roomprofile/RoomProfileSharedAction.kt | 2 +- .../roomprofile/settings/RoomSettingsController.kt | 4 ++-- .../roomprofile/settings/RoomSettingsFragment.kt | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 2204150a35..696725d001 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -99,11 +99,11 @@ class RoomProfileActivity : .observe() .subscribe { sharedAction -> when (sharedAction) { - is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() - is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() - is RoomProfileSharedAction.OpenRoomAlias -> openRoomAlias() - is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() - is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() + is RoomProfileSharedAction.OpenRoomMembers -> openRoomMembers() + is RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() + is RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() + is RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() + is RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() } } .disposeOnDestroy() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 0449f0db15..83a610cf1b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -23,7 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction */ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() - object OpenRoomAlias : RoomProfileSharedAction() + object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 6c944ef2f8..ffae4a9dc5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -46,7 +46,7 @@ class RoomSettingsController @Inject constructor( fun onNameChanged(name: String) fun onTopicChanged(topic: String) fun onHistoryVisibilityClicked() - fun onOpenAlias() + fun onRoomAliasesClicked() } private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -115,7 +115,7 @@ class RoomSettingsController @Inject constructor( dividerColor = dividerColor, divider = true, editable = true, - action = { callback?.onOpenAlias() } + action = { callback?.onRoomAliasesClicked() } ) buildProfileAction( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 01ee1cf9b8..7bfbb73909 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -168,8 +168,8 @@ class RoomSettingsFragment @Inject constructor( return@withState } - override fun onOpenAlias() { - roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAlias) + override fun onRoomAliasesClicked() { + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings) } override fun onImageReady(uri: Uri?) { From b2556cb29360312ae63fd9431470974fffe8ebe1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Nov 2020 11:00:58 +0100 Subject: [PATCH 202/231] Update change after release --- CHANGES.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 30713deb18..0e3513965f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in Element 1.0.12 (2020-XX-XX) =================================================== Features ✨: - - + - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) Improvements 🙌: - @@ -32,7 +32,6 @@ Features ✨: - Create DMs with users by scanning their QR code (#2025) - Add Invite friends quick invite actions (#2348) - Add friend by scanning QR code, show your code to friends (#2025) - - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) Improvements 🙌: - New room creation tile with quick action (#2346) From 694397efc18eaf38118328db25cc7329d70209d5 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 30 Nov 2020 13:51:40 +0300 Subject: [PATCH 203/231] Create a new pin mode for changing pin code. --- vector/src/main/java/im/vector/app/features/pin/PinFragment.kt | 1 + vector/src/main/java/im/vector/app/features/pin/PinMode.kt | 3 ++- .../vector/app/features/settings/VectorSettingsPinFragment.kt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index b6e238c2dc..378c7b853d 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -56,6 +56,7 @@ class PinFragment @Inject constructor( when (fragmentArgs.pinMode) { PinMode.CREATE -> showCreateFragment() PinMode.AUTH -> showAuthFragment() + PinMode.MODIFY -> showCreateFragment() // No need to create another function for now because texts are generic } } diff --git a/vector/src/main/java/im/vector/app/features/pin/PinMode.kt b/vector/src/main/java/im/vector/app/features/pin/PinMode.kt index c24ac5adf2..9801912bd6 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinMode.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinMode.kt @@ -18,5 +18,6 @@ package im.vector.app.features.pin enum class PinMode { CREATE, - AUTH + AUTH, + MODIFY } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index 94328dc44a..1a04dab950 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -85,7 +85,7 @@ class VectorSettingsPinFragment @Inject constructor( navigator.openPinCode( requireContext(), pinActivityResultLauncher, - PinMode.CREATE + PinMode.MODIFY ) } true From 096abd7ebf76b171d2be5fbdf8b30474894b1ae3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Nov 2020 13:33:29 +0100 Subject: [PATCH 204/231] Fix IllegalStateException: focus search returned a view that wasn't able to take focus! And no multiline input for room alias --- .../app/features/roomprofile/alias/RoomAliasController.kt | 2 ++ vector/src/main/res/layout/item_form_text_input.xml | 2 ++ vector/src/main/res/layout/item_room_alias_text_input.xml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index 49bccd79db..0b695031c5 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.alias +import android.text.InputType import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Loading @@ -169,6 +170,7 @@ class RoomAliasController @Inject constructor( value(data.publishManuallyState.value) showBottomSeparator(false) hint(stringProvider.getString(R.string.room_alias_address_hint)) + inputType(InputType.TYPE_CLASS_TEXT) onTextChange { text -> callback?.setNewAlias(text) } diff --git a/vector/src/main/res/layout/item_form_text_input.xml b/vector/src/main/res/layout/item_form_text_input.xml index 594bfc1788..f7ce8e1c9f 100644 --- a/vector/src/main/res/layout/item_form_text_input.xml +++ b/vector/src/main/res/layout/item_form_text_input.xml @@ -20,10 +20,12 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> + diff --git a/vector/src/main/res/layout/item_room_alias_text_input.xml b/vector/src/main/res/layout/item_room_alias_text_input.xml index 9216fc6b7e..fd7a99f0f0 100644 --- a/vector/src/main/res/layout/item_room_alias_text_input.xml +++ b/vector/src/main/res/layout/item_room_alias_text_input.xml @@ -30,11 +30,13 @@ app:layout_constraintStart_toEndOf="@+id/itemRoomAliasHash" app:layout_constraintTop_toTopOf="parent"> + From 2736247d09db42681e25129a6fda0570d3a25cac Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 30 Nov 2020 18:12:26 +0300 Subject: [PATCH 205/231] Move cursor to the end to fix the jumping cursor bug. Fixes #2469 --- CHANGES.md | 1 + .../main/java/im/vector/app/features/form/FormEditTextItem.kt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 83d53b75a7..fe93df3a62 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: Bugfix 🐛: - Double bottomsheet effect after verify with passphrase + - EditText cursor jumps to the start while typing fast (#2469) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index 12538d314a..eb8945d29c 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 @@ -67,6 +67,8 @@ abstract class FormEditTextItem : VectorEpoxyModel() { // Update only if text is different and value is not null if (value != null && holder.textInputEditText.text.toString() != value) { holder.textInputEditText.setText(value) + // To fix jumping cursor to the start https://github.com/airbnb/epoxy/issues/426 + holder.textInputEditText.setSelection(value?.length ?: 0) } holder.textInputEditText.isEnabled = enabled inputType?.let { holder.textInputEditText.inputType = it } From d07a95204b2cb0230588cfbc45258411cd1c2301 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 17:13:56 +0100 Subject: [PATCH 206/231] Better name --- .../features/roomprofile/settings/RoomSettingsController.kt | 4 ++-- .../features/roomprofile/settings/RoomSettingsViewModel.kt | 2 +- .../features/roomprofile/settings/RoomSettingsViewState.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index ffae4a9dc5..d5fe3ff720 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -124,8 +124,8 @@ class RoomSettingsController @Inject constructor( subtitle = newHistoryVisibility ?: historyVisibility, dividerColor = dividerColor, divider = false, - editable = data.actionPermissions.canChangeHistoryReadability, - action = { if (data.actionPermissions.canChangeHistoryReadability) callback?.onHistoryVisibilityClicked() } + editable = data.actionPermissions.canChangeHistoryVisibility, + action = { if (data.actionPermissions.canChangeHistoryVisibility) callback?.onHistoryVisibilityClicked() } ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 7e76f85d44..521cc7a662 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -109,7 +109,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canChangeAvatar = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_AVATAR), canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), - canChangeHistoryReadability = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_HISTORY_VISIBILITY) ) setState { copy(actionPermissions = permissions) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index bdcd9e6bc7..7a98201680 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -45,7 +45,7 @@ data class RoomSettingsViewState( val canChangeAvatar: Boolean = false, val canChangeName: Boolean = false, val canChangeTopic: Boolean = false, - val canChangeHistoryReadability: Boolean = false + val canChangeHistoryVisibility: Boolean = false ) sealed class AvatarAction { From bb5d5ffc92bd982629151d2f68f5503dba3bb84d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 17:35:58 +0100 Subject: [PATCH 207/231] Improve room history visibility setting UX (#1579) And observe correctly the state event --- CHANGES.md | 1 + .../settings/RoomSettingsController.kt | 14 +++----------- .../settings/RoomSettingsFragment.kt | 3 +-- .../settings/RoomSettingsViewModel.kt | 18 ++++++++++++++++-- .../settings/RoomSettingsViewState.kt | 4 ++-- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 83d53b75a7..5e4cd70194 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -43,6 +43,7 @@ Improvements 🙌: - Move "Enable Encryption" from room setting screen to room profile screen (#2394) - Home empty screens quick design update (#2347) - Improve Invite user screen (seamless search for matrix ID) + - Improve room history visibility setting UX (#1579) Bugfix 🐛: - Fix crash on AttachmentViewer (#2365) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index d5fe3ff720..c3c74f0d7c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -26,9 +26,6 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -60,9 +57,6 @@ class RoomSettingsController @Inject constructor( override fun buildModels(data: RoomSettingsViewState?) { val roomSummary = data?.roomSummary?.invoke() ?: return - val historyVisibility = data.historyVisibilityEvent?.let { formatRoomHistoryVisibilityEvent(it) } ?: "" - val newHistoryVisibility = data.newHistoryVisibility?.let { roomHistoryVisibilityFormatter.format(it) } - formEditableAvatarItem { id("avatar") enabled(data.actionPermissions.canChangeAvatar) @@ -118,6 +112,9 @@ class RoomSettingsController @Inject constructor( action = { callback?.onRoomAliasesClicked() } ) + val historyVisibility = roomHistoryVisibilityFormatter.format(data.currentHistoryVisibility) + val newHistoryVisibility = data.newHistoryVisibility?.let { roomHistoryVisibilityFormatter.format(it) } + buildProfileAction( id = "historyReadability", title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title), @@ -128,9 +125,4 @@ class RoomSettingsController @Inject constructor( action = { if (data.actionPermissions.canChangeHistoryVisibility) callback?.onHistoryVisibilityClicked() } ) } - - private fun formatRoomHistoryVisibilityEvent(event: Event): String? { - val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null - return roomHistoryVisibilityFormatter.format(historyVisibility) - } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 7bfbb73909..785368b2ff 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -147,8 +147,7 @@ class RoomSettingsFragment @Inject constructor( RoomHistoryVisibility.JOINED, RoomHistoryVisibility.WORLD_READABLE ) - val currentHistoryVisibility = - state.newHistoryVisibility ?: state.historyVisibilityEvent?.getClearContent().toModel()?.historyVisibility + val currentHistoryVisibility = state.newHistoryVisibility ?: state.currentHistoryVisibility val currentHistoryVisibilityIndex = historyVisibilities.indexOf(currentHistoryVisibility) AlertDialog.Builder(requireContext()).apply { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 521cc7a662..699e8ef6e2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.rx.mapOptional import org.matrix.android.sdk.rx.rx @@ -60,6 +61,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: init { observeRoomSummary() + observeRoomHistoryVisibility() observeRoomAvatar() observeState() } @@ -81,7 +83,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: showSaveAction = avatarAction !is RoomSettingsViewState.AvatarAction.None || summary?.name != newName || summary?.topic != newTopic - || newHistoryVisibility != null + || (newHistoryVisibility != null && newHistoryVisibility != currentHistoryVisibility) ) } } @@ -93,7 +95,6 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: .execute { async -> val roomSummary = async.invoke() copy( - historyVisibilityEvent = room.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY), roomSummary = async, newName = roomSummary?.name, newTopic = roomSummary?.topic @@ -117,6 +118,19 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: .disposeOnClear() } + private fun observeRoomHistoryVisibility() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + it.historyVisibility?.let { + setState { copy(currentHistoryVisibility = it) } + } + } + .disposeOnClear() + } + /** * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 7a98201680..fc9393d141 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -21,13 +21,13 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomSummary data class RoomSettingsViewState( val roomId: String, - val historyVisibilityEvent: Event? = null, + // Default value: https://matrix.org/docs/spec/client_server/r0.6.1#id88 + val currentHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.SHARED, val roomSummary: Async = Uninitialized, val isLoading: Boolean = false, val currentRoomAvatarUrl: String? = null, From 4171311095431da036ea2f18f9916889fa9ad361 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 19:03:49 +0100 Subject: [PATCH 208/231] Room history visibility: from ugly dialog to nice bottom sheet. Including a reusable bottom sheet pattern, for future join rules setting bottom sheet --- .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../im/vector/app/core/di/ViewModelModule.kt | 6 ++ .../core/ui/bottomsheet/BottomSheetGeneric.kt | 61 ++++++++++++++++ .../bottomsheet/BottomSheetGenericAction.kt | 43 +++++++++++ .../BottomSheetGenericController.kt | 43 +++++++++++ ...BottomSheetGenericSharedActionViewModel.kt | 25 +++++++ .../ui/bottomsheet/BottomSheetGenericState.kt | 21 ++++++ .../format/RoomHistoryVisibilityFormatter.kt | 14 ++-- .../settings/RoomSettingsFragment.kt | 48 +++++-------- .../BottomSheetRoomHistoryVisibilityAction.kt | 35 +++++++++ .../RoomHistoryVisibilityBottomSheet.kt | 71 +++++++++++++++++++ .../RoomHistoryVisibilityController.kt | 45 ++++++++++++ .../RoomHistoryVisibilityState.kt | 27 +++++++ .../RoomHistoryVisibilityViewModel.kt | 47 ++++++++++++ ...mHistoryVisibilitySharedActionViewModel.kt | 23 ++++++ 15 files changed, 477 insertions(+), 34 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt create mode 100644 vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt create mode 100644 vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt create mode 100644 vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt 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 2518e32ce5..92a7c77aa9 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 @@ -68,6 +68,7 @@ import im.vector.app.features.roommemberprofile.RoomMemberProfileActivity import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet +import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.share.IncomingShareActivity @@ -155,6 +156,7 @@ interface ScreenComponent { fun inject(bottomSheet: DisplayReadReceiptsBottomSheet) fun inject(bottomSheet: RoomListQuickActionsBottomSheet) fun inject(bottomSheet: RoomAliasBottomSheet) + fun inject(bottomSheet: RoomHistoryVisibilityBottomSheet) fun inject(bottomSheet: VerificationBottomSheet) fun inject(bottomSheet: DeviceVerificationInfoBottomSheet) fun inject(bottomSheet: DeviceListBottomSheet) diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 3399f98d43..5ef4ccbf3a 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -36,6 +36,7 @@ import im.vector.app.features.reactions.EmojiChooserViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.SetRoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel @Module @@ -111,6 +112,11 @@ interface ViewModelModule { @ViewModelKey(RoomAliasBottomSheetSharedActionViewModel::class) fun bindRoomAliasBottomSheetSharedActionViewModel(viewModel: RoomAliasBottomSheetSharedActionViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(SetRoomHistoryVisibilitySharedActionViewModel::class) + fun bindSetRoomHistoryVisibilitySharedActionViewModel(viewModel: SetRoomHistoryVisibilitySharedActionViewModel): ViewModel + @Binds @IntoMap @ViewModelKey(RoomDirectorySharedActionViewModel::class) diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt new file mode 100644 index 0000000000..d2e1495f1b --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt @@ -0,0 +1,61 @@ +/* + * 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.core.ui.bottomsheet + +import android.os.Bundle +import android.view.View +import androidx.annotation.CallSuper +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import im.vector.app.R +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import javax.inject.Inject + +/** + * Generic Bottom sheet with actions + */ +abstract class BottomSheetGeneric : + VectorBaseBottomSheetDialogFragment(), + BottomSheetGenericController.Listener { + + @Inject lateinit var sharedViewPool: RecyclerView.RecycledViewPool + + @BindView(R.id.bottomSheetRecyclerView) + lateinit var recyclerView: RecyclerView + + final override val showExpanded = true + + final override fun getLayoutResId() = R.layout.bottom_sheet_generic_list + + abstract fun getController(): BottomSheetGenericController + + @CallSuper + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + recyclerView.configureWith(getController(), viewPool = sharedViewPool, hasFixedSize = false, disableItemAnimation = true) + getController().listener = this + } + + @CallSuper + override fun onDestroyView() { + recyclerView.cleanup() + getController().listener = null + super.onDestroyView() + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt new file mode 100644 index 0000000000..50a04a8fe1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericAction.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.ui.bottomsheet + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import im.vector.app.core.epoxy.bottomsheet.BottomSheetActionItem_ +import im.vector.app.core.platform.VectorSharedAction + +/** + * Parent class for a bottom sheet action + */ +open class BottomSheetGenericAction( + @StringRes open val titleRes: Int, + @DrawableRes open val iconResId: Int, + open val isSelected: Boolean, + open val destructive: Boolean +) : VectorSharedAction { + + fun toBottomSheetItem(): BottomSheetActionItem_ { + return BottomSheetActionItem_().apply { + id("action_$titleRes") + iconRes(iconResId) + textRes(titleRes) + selected(isSelected) + destructive(destructive) + } + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt new file mode 100644 index 0000000000..2dfa4fc93a --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt @@ -0,0 +1,43 @@ +/* + * 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.core.ui.bottomsheet + +import android.view.View +import com.airbnb.epoxy.TypedEpoxyController + +/** + * Epoxy controller for generic bottom sheet actions + */ +abstract class BottomSheetGenericController + : TypedEpoxyController() { + + var listener: Listener? = null + + abstract fun getActions(state: State): List + + override fun buildModels(state: State?) { + state ?: return + getActions(state).forEach { action -> + action.toBottomSheetItem() + .listener(View.OnClickListener { listener?.didSelectAction(action) }) + .addTo(this) + } + } + + interface Listener { + fun didSelectAction(action: Action) + } +} diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt new file mode 100644 index 0000000000..ba8bd4abba --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.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.core.ui.bottomsheet + +import im.vector.app.core.platform.VectorSharedAction +import im.vector.app.core.platform.VectorSharedActionViewModel + +/** + * Activity shared view model to handle bottom sheet quick actions + */ +abstract class BottomSheetGenericSharedActionViewModel : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt new file mode 100644 index 0000000000..b01216afad --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt @@ -0,0 +1,21 @@ +/* + * 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.core.ui.bottomsheet + +import com.airbnb.mvrx.MvRxState + +abstract class BottomSheetGenericState : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt index 4563e6a6ed..e2dc3932b2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.format +import androidx.annotation.StringRes import im.vector.app.R import im.vector.app.core.resources.StringProvider import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -26,11 +27,16 @@ class RoomHistoryVisibilityFormatter @Inject constructor( ) { fun format(roomHistoryVisibility: RoomHistoryVisibility): String { + return stringProvider.getString(getStringResId(roomHistoryVisibility)) + } + + @StringRes + fun getStringResId(roomHistoryVisibility: RoomHistoryVisibility): Int { return when (roomHistoryVisibility) { - RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) - RoomHistoryVisibility.INVITED -> stringProvider.getString(R.string.notice_room_visibility_invited) - RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) - RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) + RoomHistoryVisibility.SHARED -> R.string.notice_room_visibility_shared + RoomHistoryVisibility.INVITED -> R.string.notice_room_visibility_invited + RoomHistoryVisibility.JOINED -> R.string.notice_room_visibility_joined + RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 785368b2ff..4c51186623 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -37,15 +37,13 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.toast import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileSharedAction import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.SetRoomHistoryVisibilitySharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.util.toMatrixItem import java.util.UUID import javax.inject.Inject @@ -53,7 +51,6 @@ import javax.inject.Inject class RoomSettingsFragment @Inject constructor( val viewModelFactory: RoomSettingsViewModel.Factory, private val controller: RoomSettingsController, - private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter, colorProvider: ColorProvider, private val avatarRenderer: AvatarRenderer ) : @@ -64,6 +61,7 @@ class RoomSettingsFragment @Inject constructor( private val viewModel: RoomSettingsViewModel by fragmentViewModel() private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel + private lateinit var sharedActionViewModel: SetRoomHistoryVisibilitySharedActionViewModel private val roomProfileArgs: RoomProfileArgs by args() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) @@ -74,6 +72,7 @@ class RoomSettingsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) + setupRoomHistoryVisibilitySharedViewModel() controller.callback = this setupToolbar(roomSettingsToolbar) roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) @@ -92,6 +91,16 @@ class RoomSettingsFragment @Inject constructor( } } + private fun setupRoomHistoryVisibilitySharedViewModel() { + sharedActionViewModel = activityViewModelProvider.get(SetRoomHistoryVisibilitySharedActionViewModel::class.java) + sharedActionViewModel + .observe() + .subscribe { action -> + viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility)) + } + .disposeOnDestroyView() + } + private fun showSuccess() { activity?.toast(R.string.room_settings_save_success) } @@ -141,30 +150,9 @@ class RoomSettingsFragment @Inject constructor( } override fun onHistoryVisibilityClicked() = withState(viewModel) { state -> - val historyVisibilities = arrayOf( - RoomHistoryVisibility.SHARED, - RoomHistoryVisibility.INVITED, - RoomHistoryVisibility.JOINED, - RoomHistoryVisibility.WORLD_READABLE - ) val currentHistoryVisibility = state.newHistoryVisibility ?: state.currentHistoryVisibility - val currentHistoryVisibilityIndex = historyVisibilities.indexOf(currentHistoryVisibility) - - AlertDialog.Builder(requireContext()).apply { - setTitle(R.string.room_settings_room_read_history_rules_pref_title) - setSingleChoiceItems( - historyVisibilities - .map { roomHistoryVisibilityFormatter.format(it) } - .toTypedArray(), - currentHistoryVisibilityIndex) { dialog, which -> - if (which != currentHistoryVisibilityIndex) { - viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(historyVisibilities[which])) - } - dialog.cancel() - } - show() - } - return@withState + RoomHistoryVisibilityBottomSheet.newInstance(currentHistoryVisibility) + .show(childFragmentManager, "RoomHistoryVisibilityBottomSheet") } override fun onRoomAliasesClicked() { @@ -185,10 +173,10 @@ class RoomSettingsFragment @Inject constructor( override fun onAvatarDelete() { withState(viewModel) { when (it.avatarAction) { - RoomSettingsViewState.AvatarAction.None -> { + RoomSettingsViewState.AvatarAction.None -> { viewModel.handle(RoomSettingsAction.SetAvatarAction(RoomSettingsViewState.AvatarAction.DeleteAvatar)) } - RoomSettingsViewState.AvatarAction.DeleteAvatar -> { + RoomSettingsViewState.AvatarAction.DeleteAvatar -> { /* Should not happen */ Unit } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt new file mode 100644 index 0000000000..45e7188952 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.historyvisibility + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericAction +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility + +class BottomSheetRoomHistoryVisibilityAction( + val roomHistoryVisibility: RoomHistoryVisibility, + @StringRes titleRes: Int, + @DrawableRes iconResId: Int, + isSelected: Boolean, + destructive: Boolean +) : BottomSheetGenericAction( + titleRes = titleRes, + iconResId = iconResId, + isSelected = isSelected, + destructive = destructive +) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt new file mode 100644 index 0000000000..bf837516ad --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.historyvisibility + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.ui.bottomsheet.BottomSheetGeneric +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import kotlinx.android.parcel.Parcelize +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import javax.inject.Inject + +@Parcelize +data class RoomHistoryVisibilityBottomSheetArgs( + val currentRoomHistoryVisibility: RoomHistoryVisibility +) : Parcelable + +class RoomHistoryVisibilityBottomSheet : BottomSheetGeneric() { + + private lateinit var roomHistoryVisibilitySharedActionViewModel: SetRoomHistoryVisibilitySharedActionViewModel + @Inject lateinit var controller: RoomHistoryVisibilityController + @Inject lateinit var roomHistoryVisibilityViewModelFactory: RoomHistoryVisibilityViewModel.Factory + private val viewModel: RoomHistoryVisibilityViewModel by fragmentViewModel(RoomHistoryVisibilityViewModel::class) + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getController(): BottomSheetGenericController = controller + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(SetRoomHistoryVisibilitySharedActionViewModel::class.java) + } + + override fun didSelectAction(action: BottomSheetRoomHistoryVisibilityAction) { + roomHistoryVisibilitySharedActionViewModel.post(action) + dismiss() + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + super.invalidate() + } + + companion object { + fun newInstance(currentRoomHistoryVisibility: RoomHistoryVisibility): RoomHistoryVisibilityBottomSheet { + return RoomHistoryVisibilityBottomSheet().apply { + setArguments(RoomHistoryVisibilityBottomSheetArgs(currentRoomHistoryVisibility)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt new file mode 100644 index 0000000000..bdb5f80198 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.historyvisibility + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import javax.inject.Inject + +class RoomHistoryVisibilityController @Inject constructor( + private val historyVisibilityFormatter: RoomHistoryVisibilityFormatter +) : BottomSheetGenericController() { + + override fun getActions(state: RoomHistoryVisibilityState): List { + return listOf( + RoomHistoryVisibility.SHARED, + RoomHistoryVisibility.INVITED, + RoomHistoryVisibility.JOINED, + RoomHistoryVisibility.WORLD_READABLE + ) + .map { roomHistoryVisibility -> + BottomSheetRoomHistoryVisibilityAction( + roomHistoryVisibility = roomHistoryVisibility, + titleRes = historyVisibilityFormatter.getStringResId(roomHistoryVisibility), + iconResId = 0, + isSelected = roomHistoryVisibility == state.currentRoomHistoryVisibility, + destructive = false + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt new file mode 100644 index 0000000000..0b651d5664 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.historyvisibility + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericState +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility + +data class RoomHistoryVisibilityState( + val currentRoomHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.SHARED +) : BottomSheetGenericState() { + + constructor(args: RoomHistoryVisibilityBottomSheetArgs) : this(currentRoomHistoryVisibility = args.currentRoomHistoryVisibility) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt new file mode 100644 index 0000000000..ce11f7b235 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.historyvisibility + +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel + +class RoomHistoryVisibilityViewModel @AssistedInject constructor(@Assisted initialState: RoomHistoryVisibilityState) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomHistoryVisibilityState): RoomHistoryVisibilityViewModel + } + + companion object : MvRxViewModelFactory { + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomHistoryVisibilityState): RoomHistoryVisibilityViewModel? { + val fragment: RoomHistoryVisibilityBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.roomHistoryVisibilityViewModelFactory.create(state) + } + } + + override fun handle(action: EmptyAction) { + // No op + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt new file mode 100644 index 0000000000..7f447f2384 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.features.roomprofile.settings.historyvisibility + +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +class SetRoomHistoryVisibilitySharedActionViewModel @Inject constructor() + : VectorSharedActionViewModel() From 637c54073ac52f0ae5286fc2d322699af1c18b19 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 20:49:23 +0100 Subject: [PATCH 209/231] Room history visibility: Use correct wording for the setting. --- .../bottomsheet/BottomSheetActionItem.kt | 11 ++++++++- .../bottomsheet/BottomSheetGenericAction.kt | 7 +++--- .../timeline/format/NoticeEventFormatter.kt | 6 ++--- .../format/RoomHistoryVisibilityFormatter.kt | 24 ++++++++++--------- .../settings/RoomSettingsController.kt | 5 +--- .../BottomSheetRoomHistoryVisibilityAction.kt | 5 ++-- .../RoomHistoryVisibilityController.kt | 2 +- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt index e28bec6874..3666cabce3 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt @@ -21,6 +21,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.view.isInvisible @@ -43,6 +44,10 @@ abstract class BottomSheetActionItem : VectorEpoxyModel()?.historyVisibility ?: return null - val formattedVisibility = roomHistoryVisibilityFormatter.format(historyVisibility) + val historyVisibilitySuffix = roomHistoryVisibilityFormatter.getNoticeSuffix(historyVisibility) return if (event.isSentByCurrentUser()) { sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you, - formattedVisibility) + historyVisibilitySuffix) } else { sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility, - senderName, formattedVisibility) + senderName, historyVisibilitySuffix) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt index e2dc3932b2..14769bc95b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/RoomHistoryVisibilityFormatter.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home.room.detail.timeline.format -import androidx.annotation.StringRes import im.vector.app.R import im.vector.app.core.resources.StringProvider import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -25,18 +24,21 @@ import javax.inject.Inject class RoomHistoryVisibilityFormatter @Inject constructor( private val stringProvider: StringProvider ) { - - fun format(roomHistoryVisibility: RoomHistoryVisibility): String { - return stringProvider.getString(getStringResId(roomHistoryVisibility)) - } - - @StringRes - fun getStringResId(roomHistoryVisibility: RoomHistoryVisibility): Int { - return when (roomHistoryVisibility) { + fun getNoticeSuffix(roomHistoryVisibility: RoomHistoryVisibility): String { + return stringProvider.getString(when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable RoomHistoryVisibility.SHARED -> R.string.notice_room_visibility_shared RoomHistoryVisibility.INVITED -> R.string.notice_room_visibility_invited RoomHistoryVisibility.JOINED -> R.string.notice_room_visibility_joined - RoomHistoryVisibility.WORLD_READABLE -> R.string.notice_room_visibility_world_readable - } + }) + } + + fun getSetting(roomHistoryVisibility: RoomHistoryVisibility): String { + return stringProvider.getString(when (roomHistoryVisibility) { + RoomHistoryVisibility.WORLD_READABLE -> R.string.room_settings_read_history_entry_anyone + RoomHistoryVisibility.SHARED -> R.string.room_settings_read_history_entry_members_only_option_time_shared + RoomHistoryVisibility.INVITED -> R.string.room_settings_read_history_entry_members_only_invited + RoomHistoryVisibility.JOINED -> R.string.room_settings_read_history_entry_members_only_joined + }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index c3c74f0d7c..96ae07e0e9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -112,13 +112,10 @@ class RoomSettingsController @Inject constructor( action = { callback?.onRoomAliasesClicked() } ) - val historyVisibility = roomHistoryVisibilityFormatter.format(data.currentHistoryVisibility) - val newHistoryVisibility = data.newHistoryVisibility?.let { roomHistoryVisibilityFormatter.format(it) } - buildProfileAction( id = "historyReadability", title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title), - subtitle = newHistoryVisibility ?: historyVisibility, + subtitle = roomHistoryVisibilityFormatter.getSetting(data.newHistoryVisibility ?: data.currentHistoryVisibility), dividerColor = dividerColor, divider = false, editable = data.actionPermissions.canChangeHistoryVisibility, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt index 45e7188952..9fc27070b3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt @@ -17,18 +17,17 @@ package im.vector.app.features.roomprofile.settings.historyvisibility import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import im.vector.app.core.ui.bottomsheet.BottomSheetGenericAction import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility class BottomSheetRoomHistoryVisibilityAction( val roomHistoryVisibility: RoomHistoryVisibility, - @StringRes titleRes: Int, + title: String, @DrawableRes iconResId: Int, isSelected: Boolean, destructive: Boolean ) : BottomSheetGenericAction( - titleRes = titleRes, + title = title, iconResId = iconResId, isSelected = isSelected, destructive = destructive diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt index bdb5f80198..34b9f8571c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -35,7 +35,7 @@ class RoomHistoryVisibilityController @Inject constructor( .map { roomHistoryVisibility -> BottomSheetRoomHistoryVisibilityAction( roomHistoryVisibility = roomHistoryVisibility, - titleRes = historyVisibilityFormatter.getStringResId(roomHistoryVisibility), + title = historyVisibilityFormatter.getSetting(roomHistoryVisibility), iconResId = 0, isSelected = roomHistoryVisibility == state.currentRoomHistoryVisibility, destructive = false From 63b068f426a56c7c02b8d0274878c4e2d5d24e31 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 20:52:46 +0100 Subject: [PATCH 210/231] Reorder iso Element Web --- .../historyvisibility/RoomHistoryVisibilityController.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt index 34b9f8571c..edfe6b34ac 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -27,10 +27,10 @@ class RoomHistoryVisibilityController @Inject constructor( override fun getActions(state: RoomHistoryVisibilityState): List { return listOf( + RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibility.SHARED, RoomHistoryVisibility.INVITED, - RoomHistoryVisibility.JOINED, - RoomHistoryVisibility.WORLD_READABLE + RoomHistoryVisibility.JOINED ) .map { roomHistoryVisibility -> BottomSheetRoomHistoryVisibilityAction( From 41dd67f1c1bb373e09381b1aa2c0d50585180bae Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 20:58:41 +0100 Subject: [PATCH 211/231] Renaming --- .../java/im/vector/app/core/di/ViewModelModule.kt | 6 +++--- .../roomprofile/settings/RoomSettingsFragment.kt | 12 ++++++------ ...ilityAction.kt => RoomHistoryVisibilityAction.kt} | 2 +- .../RoomHistoryVisibilityBottomSheet.kt | 10 +++++----- .../RoomHistoryVisibilityController.kt | 6 +++--- ...=> RoomHistoryVisibilitySharedActionViewModel.kt} | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/{BottomSheetRoomHistoryVisibilityAction.kt => RoomHistoryVisibilityAction.kt} (96%) rename vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/{SetRoomHistoryVisibilitySharedActionViewModel.kt => RoomHistoryVisibilitySharedActionViewModel.kt} (83%) diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 5ef4ccbf3a..6751cf5c97 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -36,7 +36,7 @@ import im.vector.app.features.reactions.EmojiChooserViewModel import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel -import im.vector.app.features.roomprofile.settings.historyvisibility.SetRoomHistoryVisibilitySharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel @Module @@ -114,8 +114,8 @@ interface ViewModelModule { @Binds @IntoMap - @ViewModelKey(SetRoomHistoryVisibilitySharedActionViewModel::class) - fun bindSetRoomHistoryVisibilitySharedActionViewModel(viewModel: SetRoomHistoryVisibilitySharedActionViewModel): ViewModel + @ViewModelKey(RoomHistoryVisibilitySharedActionViewModel::class) + fun bindRoomHistoryVisibilitySharedActionViewModel(viewModel: RoomHistoryVisibilitySharedActionViewModel): ViewModel @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 4c51186623..5dbf8470df 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -40,7 +40,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileSharedAction import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel -import im.vector.app.features.roomprofile.settings.historyvisibility.SetRoomHistoryVisibilitySharedActionViewModel +import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* @@ -61,7 +61,7 @@ class RoomSettingsFragment @Inject constructor( private val viewModel: RoomSettingsViewModel by fragmentViewModel() private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel - private lateinit var sharedActionViewModel: SetRoomHistoryVisibilitySharedActionViewModel + private lateinit var roomHistoryVisibilitySharedActionViewModel: RoomHistoryVisibilitySharedActionViewModel private val roomProfileArgs: RoomProfileArgs by args() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) @@ -72,7 +72,7 @@ class RoomSettingsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) - setupRoomHistoryVisibilitySharedViewModel() + setupRoomHistoryVisibilitySharedActionViewModel() controller.callback = this setupToolbar(roomSettingsToolbar) roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) @@ -91,9 +91,9 @@ class RoomSettingsFragment @Inject constructor( } } - private fun setupRoomHistoryVisibilitySharedViewModel() { - sharedActionViewModel = activityViewModelProvider.get(SetRoomHistoryVisibilitySharedActionViewModel::class.java) - sharedActionViewModel + private fun setupRoomHistoryVisibilitySharedActionViewModel() { + roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) + roomHistoryVisibilitySharedActionViewModel .observe() .subscribe { action -> viewModel.handle(RoomSettingsAction.SetRoomHistoryVisibility(action.roomHistoryVisibility)) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt similarity index 96% rename from vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt index 9fc27070b3..f37964ae23 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/BottomSheetRoomHistoryVisibilityAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt @@ -20,7 +20,7 @@ import androidx.annotation.DrawableRes import im.vector.app.core.ui.bottomsheet.BottomSheetGenericAction import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -class BottomSheetRoomHistoryVisibilityAction( +class RoomHistoryVisibilityAction( val roomHistoryVisibility: RoomHistoryVisibility, title: String, @DrawableRes iconResId: Int, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt index bf837516ad..813a58838c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityBottomSheet.kt @@ -33,9 +33,9 @@ data class RoomHistoryVisibilityBottomSheetArgs( val currentRoomHistoryVisibility: RoomHistoryVisibility ) : Parcelable -class RoomHistoryVisibilityBottomSheet : BottomSheetGeneric() { +class RoomHistoryVisibilityBottomSheet : BottomSheetGeneric() { - private lateinit var roomHistoryVisibilitySharedActionViewModel: SetRoomHistoryVisibilitySharedActionViewModel + private lateinit var roomHistoryVisibilitySharedActionViewModel: RoomHistoryVisibilitySharedActionViewModel @Inject lateinit var controller: RoomHistoryVisibilityController @Inject lateinit var roomHistoryVisibilityViewModelFactory: RoomHistoryVisibilityViewModel.Factory private val viewModel: RoomHistoryVisibilityViewModel by fragmentViewModel(RoomHistoryVisibilityViewModel::class) @@ -44,14 +44,14 @@ class RoomHistoryVisibilityBottomSheet : BottomSheetGeneric = controller + override fun getController(): BottomSheetGenericController = controller override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(SetRoomHistoryVisibilitySharedActionViewModel::class.java) + roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) } - override fun didSelectAction(action: BottomSheetRoomHistoryVisibilityAction) { + override fun didSelectAction(action: RoomHistoryVisibilityAction) { roomHistoryVisibilitySharedActionViewModel.post(action) dismiss() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt index edfe6b34ac..9c9abc03a1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -23,9 +23,9 @@ import javax.inject.Inject class RoomHistoryVisibilityController @Inject constructor( private val historyVisibilityFormatter: RoomHistoryVisibilityFormatter -) : BottomSheetGenericController() { +) : BottomSheetGenericController() { - override fun getActions(state: RoomHistoryVisibilityState): List { + override fun getActions(state: RoomHistoryVisibilityState): List { return listOf( RoomHistoryVisibility.WORLD_READABLE, RoomHistoryVisibility.SHARED, @@ -33,7 +33,7 @@ class RoomHistoryVisibilityController @Inject constructor( RoomHistoryVisibility.JOINED ) .map { roomHistoryVisibility -> - BottomSheetRoomHistoryVisibilityAction( + RoomHistoryVisibilityAction( roomHistoryVisibility = roomHistoryVisibility, title = historyVisibilityFormatter.getSetting(roomHistoryVisibility), iconResId = 0, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt similarity index 83% rename from vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt index 7f447f2384..31c1c2631c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/SetRoomHistoryVisibilitySharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilitySharedActionViewModel.kt @@ -19,5 +19,5 @@ package im.vector.app.features.roomprofile.settings.historyvisibility import im.vector.app.core.platform.VectorSharedActionViewModel import javax.inject.Inject -class SetRoomHistoryVisibilitySharedActionViewModel @Inject constructor() - : VectorSharedActionViewModel() +class RoomHistoryVisibilitySharedActionViewModel @Inject constructor() + : VectorSharedActionViewModel() From a813610c04da27090bbe89b973cc691582836e9f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 22:23:25 +0100 Subject: [PATCH 212/231] Room setting: update join rules and guest access (#2442) --- CHANGES.md | 1 + .../java/org/matrix/android/sdk/rx/RxRoom.kt | 6 ++ .../api/session/room/state/StateService.kt | 7 ++ .../session/room/state/DefaultStateService.kt | 29 ++++++++ .../im/vector/app/core/di/ScreenComponent.kt | 2 + .../im/vector/app/core/di/ViewModelModule.kt | 6 ++ .../settings/RoomSettingsAction.kt | 4 + .../settings/RoomSettingsController.kt | 29 +++++++- .../settings/RoomSettingsFragment.kt | 22 ++++++ .../settings/RoomSettingsViewModel.kt | 56 +++++++++++++- .../settings/RoomSettingsViewState.kt | 15 +++- .../settings/joinrule/RoomJoinRuleAction.kt | 36 +++++++++ .../joinrule/RoomJoinRuleBottomSheet.kt | 73 +++++++++++++++++++ .../joinrule/RoomJoinRuleController.kt | 58 +++++++++++++++ .../RoomJoinRuleSharedActionViewModel.kt | 23 ++++++ .../settings/joinrule/RoomJoinRuleState.kt | 32 ++++++++ .../joinrule/RoomJoinRuleViewModel.kt | 47 ++++++++++++ vector/src/main/res/values/strings.xml | 1 + 18 files changed, 443 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt diff --git a/CHANGES.md b/CHANGES.md index 5e4cd70194..86258ecc57 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ Features ✨: - Create DMs with users by scanning their QR code (#2025) - Add Invite friends quick invite actions (#2348) - Add friend by scanning QR code, show your code to friends (#2025) + - Room setting: update join rules and guest access (#2442) Improvements 🙌: - New room creation tile with quick action (#2346) 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 826ada358b..bf4bcacc31 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -35,6 +35,8 @@ import org.matrix.android.sdk.api.util.toOptional import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules class RxRoom(private val room: Room) { @@ -131,6 +133,10 @@ class RxRoom(private val room: Room) { room.updateHistoryReadability(readability, it) } + fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = completableBuilder { + room.updateJoinRule(joinRules, guestAccess, it) + } + fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder { room.updateAvatar(avatarUri, fileName, it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 1d048f2459..74e3faf38a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -21,7 +21,9 @@ import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -50,6 +52,11 @@ interface StateService { */ fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback): Cancelable + /** + * Update the join rule and/or the guest access + */ + fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback): Cancelable + /** * Update the avatar of the room */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 3d6e869607..6015d945c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -25,8 +25,12 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict @@ -133,6 +137,31 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private ) } + override fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback): Cancelable { + return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { + if (joinRules != null) { + awaitCallback { + sendStateEvent( + eventType = EventType.STATE_ROOM_JOIN_RULES, + body = RoomJoinRulesContent(joinRules).toContent(), + callback = it, + stateKey = null + ) + } + } + if (guestAccess != null) { + awaitCallback { + sendStateEvent( + eventType = EventType.STATE_ROOM_GUEST_ACCESS, + body = RoomGuestAccessContent(guestAccess).toContent(), + callback = it, + stateKey = null + ) + } + } + } + } + override fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback): Cancelable { return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { val response = fileUploader.uploadFromUri(avatarUri, fileName, "image/jpeg") diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 92a7c77aa9..f56a6a3d70 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -69,6 +69,7 @@ import im.vector.app.features.roommemberprofile.devices.DeviceListBottomSheet import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheet import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet +import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.share.IncomingShareActivity @@ -157,6 +158,7 @@ interface ScreenComponent { fun inject(bottomSheet: RoomListQuickActionsBottomSheet) fun inject(bottomSheet: RoomAliasBottomSheet) fun inject(bottomSheet: RoomHistoryVisibilityBottomSheet) + fun inject(bottomSheet: RoomJoinRuleBottomSheet) fun inject(bottomSheet: VerificationBottomSheet) fun inject(bottomSheet: DeviceVerificationInfoBottomSheet) fun inject(bottomSheet: DeviceListBottomSheet) diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt index 6751cf5c97..bed2e0b850 100644 --- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt @@ -37,6 +37,7 @@ import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.alias.detail.RoomAliasBottomSheetSharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel +import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import im.vector.app.features.userdirectory.UserListSharedActionViewModel @Module @@ -117,6 +118,11 @@ interface ViewModelModule { @ViewModelKey(RoomHistoryVisibilitySharedActionViewModel::class) fun bindRoomHistoryVisibilitySharedActionViewModel(viewModel: RoomHistoryVisibilitySharedActionViewModel): ViewModel + @Binds + @IntoMap + @ViewModelKey(RoomJoinRuleSharedActionViewModel::class) + fun bindRoomJoinRuleSharedActionViewModel(viewModel: RoomJoinRuleSharedActionViewModel): ViewModel + @Binds @IntoMap @ViewModelKey(RoomDirectorySharedActionViewModel::class) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index f88a7cbfd5..867c605030 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -17,13 +17,17 @@ package im.vector.app.features.roomprofile.settings import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules sealed class RoomSettingsAction : VectorViewModelAction { data class SetAvatarAction(val avatarAction: RoomSettingsViewState.AvatarAction) : RoomSettingsAction() data class SetRoomName(val newName: String) : RoomSettingsAction() data class SetRoomTopic(val newTopic: String) : RoomSettingsAction() data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() + data class SetRoomJoinRule(val roomJoinRule: RoomJoinRules?, val roomGuestAccess: GuestAccess?) : RoomSettingsAction() + object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 96ae07e0e9..bf3c1f87f8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -26,6 +26,8 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -44,6 +46,7 @@ class RoomSettingsController @Inject constructor( fun onTopicChanged(topic: String) fun onHistoryVisibilityClicked() fun onRoomAliasesClicked() + fun onJoinRuleClicked() } private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -117,9 +120,33 @@ class RoomSettingsController @Inject constructor( title = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_title), subtitle = roomHistoryVisibilityFormatter.getSetting(data.newHistoryVisibility ?: data.currentHistoryVisibility), dividerColor = dividerColor, - divider = false, + divider = true, editable = data.actionPermissions.canChangeHistoryVisibility, action = { if (data.actionPermissions.canChangeHistoryVisibility) callback?.onHistoryVisibilityClicked() } ) + + buildProfileAction( + id = "joinRule", + title = stringProvider.getString(R.string.room_settings_room_access_title), + subtitle = data.getJoinRuleWording(), + dividerColor = dividerColor, + divider = false, + editable = data.actionPermissions.canChangeJoinRule, + action = { if (data.actionPermissions.canChangeJoinRule) callback?.onJoinRuleClicked() } + ) + } + + private fun RoomSettingsViewState.getJoinRuleWording(): String { + val joinRule = newRoomJoinRules.newJoinRules ?: currentRoomJoinRules + val guestAccess = newRoomJoinRules.newGuestAccess ?: currentGuestAccess + return stringProvider.getString(if (joinRule == RoomJoinRules.INVITE) { + R.string.room_settings_room_access_entry_only_invited + } else { + if (guestAccess == GuestAccess.CanJoin) { + R.string.room_settings_room_access_entry_anyone_with_link_including_guest + } else { + R.string.room_settings_room_access_entry_anyone_with_link_apart_guest + } + }) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 5dbf8470df..d8c8c41936 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -42,6 +42,8 @@ import im.vector.app.features.roomprofile.RoomProfileSharedAction import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilitySharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet +import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleBottomSheet +import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleSharedActionViewModel import kotlinx.android.synthetic.main.fragment_room_setting_generic.* import kotlinx.android.synthetic.main.merge_overlay_waiting_view.* import org.matrix.android.sdk.api.util.toMatrixItem @@ -62,6 +64,8 @@ class RoomSettingsFragment @Inject constructor( private val viewModel: RoomSettingsViewModel by fragmentViewModel() private lateinit var roomProfileSharedActionViewModel: RoomProfileSharedActionViewModel private lateinit var roomHistoryVisibilitySharedActionViewModel: RoomHistoryVisibilitySharedActionViewModel + private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel + private val roomProfileArgs: RoomProfileArgs by args() private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) @@ -73,6 +77,7 @@ class RoomSettingsFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) roomProfileSharedActionViewModel = activityViewModelProvider.get(RoomProfileSharedActionViewModel::class.java) setupRoomHistoryVisibilitySharedActionViewModel() + setupRoomJoinRuleSharedActionViewModel() controller.callback = this setupToolbar(roomSettingsToolbar) roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) @@ -91,6 +96,16 @@ class RoomSettingsFragment @Inject constructor( } } + private fun setupRoomJoinRuleSharedActionViewModel() { + roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) + roomJoinRuleSharedActionViewModel + .observe() + .subscribe { action -> + viewModel.handle(RoomSettingsAction.SetRoomJoinRule(action.roomJoinRule, action.roomGuestAccess)) + } + .disposeOnDestroyView() + } + private fun setupRoomHistoryVisibilitySharedActionViewModel() { roomHistoryVisibilitySharedActionViewModel = activityViewModelProvider.get(RoomHistoryVisibilitySharedActionViewModel::class.java) roomHistoryVisibilitySharedActionViewModel @@ -159,6 +174,13 @@ class RoomSettingsFragment @Inject constructor( roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomAliasesSettings) } + override fun onJoinRuleClicked() = withState(viewModel) { state -> + val currentJoinRule = state.newRoomJoinRules.newJoinRules ?: state.currentRoomJoinRules + val currentGuestAccess = state.newRoomJoinRules.newGuestAccess ?: state.currentGuestAccess + RoomJoinRuleBottomSheet.newInstance(currentJoinRule, currentGuestAccess) + .show(childFragmentManager, "RoomJoinRuleBottomSheet") + } + override fun onImageReady(uri: Uri?) { uri ?: return viewModel.handle( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 699e8ef6e2..48ff38f92e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -33,7 +33,9 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.rx.mapOptional import org.matrix.android.sdk.rx.rx @@ -62,6 +64,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: init { observeRoomSummary() observeRoomHistoryVisibility() + observeJoinRule() + observeGuestAccess() observeRoomAvatar() observeState() } @@ -72,10 +76,12 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: RoomSettingsViewState::newName, RoomSettingsViewState::newTopic, RoomSettingsViewState::newHistoryVisibility, + RoomSettingsViewState::newRoomJoinRules, RoomSettingsViewState::roomSummary) { avatarAction, newName, newTopic, newHistoryVisibility, + newJoinRule, asyncSummary -> val summary = asyncSummary() setState { @@ -84,6 +90,7 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: || summary?.name != newName || summary?.topic != newTopic || (newHistoryVisibility != null && newHistoryVisibility != currentHistoryVisibility) + || newJoinRule.hasChanged() ) } } @@ -111,7 +118,11 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: canChangeName = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_NAME), canChangeTopic = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, EventType.STATE_ROOM_TOPIC), canChangeHistoryVisibility = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, - EventType.STATE_ROOM_HISTORY_VISIBILITY) + EventType.STATE_ROOM_HISTORY_VISIBILITY), + canChangeJoinRule = powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + EventType.STATE_ROOM_JOIN_RULES) + && powerLevelsHelper.isUserAllowedToSend(session.myUserId, true, + EventType.STATE_ROOM_GUEST_ACCESS) ) setState { copy(actionPermissions = permissions) } } @@ -131,6 +142,32 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: .disposeOnClear() } + private fun observeGuestAccess() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + it.joinRules?.let { + setState { copy(currentRoomJoinRules = it) } + } + } + .disposeOnClear() + } + + private fun observeJoinRule() { + room.rx() + .liveStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.NoCondition) + .mapOptional { it.content.toModel() } + .unwrap() + .subscribe { + it.guestAccess?.let { + setState { copy(currentGuestAccess = it) } + } + } + .disposeOnClear() + } + /** * We do not want to use the fallback avatar url, which can be the other user avatar, or the current user avatar. */ @@ -151,11 +188,21 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: is RoomSettingsAction.SetRoomName -> setState { copy(newName = action.newName) } is RoomSettingsAction.SetRoomTopic -> setState { copy(newTopic = action.newTopic) } is RoomSettingsAction.SetRoomHistoryVisibility -> setState { copy(newHistoryVisibility = action.visibility) } + is RoomSettingsAction.SetRoomJoinRule -> handleSetRoomJoinRule(action) is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() }.exhaustive } + private fun handleSetRoomJoinRule(action: RoomSettingsAction.SetRoomJoinRule) = withState { state -> + setState { + copy(newRoomJoinRules = RoomSettingsViewState.NewJoinRule( + action.roomJoinRule.takeIf { it != state.currentRoomJoinRules }, + action.roomGuestAccess.takeIf { it != state.currentGuestAccess } + )) + } + } + private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) { setState { deletePendingAvatar(this) @@ -202,6 +249,10 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: operationList.add(room.rx().updateHistoryReadability(state.newHistoryVisibility)) } + if (state.newRoomJoinRules.hasChanged()) { + operationList.add(room.rx().updateJoinRule(state.newRoomJoinRules.newJoinRules, state.newRoomJoinRules.newGuestAccess)) + } + Observable .fromIterable(operationList) .concatMapCompletable { it } @@ -212,7 +263,8 @@ class RoomSettingsViewModel @AssistedInject constructor(@Assisted initialState: deletePendingAvatar(this) copy( avatarAction = RoomSettingsViewState.AvatarAction.None, - newHistoryVisibility = null + newHistoryVisibility = null, + newRoomJoinRules = RoomSettingsViewState.NewJoinRule() ) } _viewEvents.post(RoomSettingsViewEvents.Success) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index fc9393d141..7403917d48 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -21,13 +21,17 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.roomprofile.RoomProfileArgs +import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary data class RoomSettingsViewState( val roomId: String, // Default value: https://matrix.org/docs/spec/client_server/r0.6.1#id88 val currentHistoryVisibility: RoomHistoryVisibility = RoomHistoryVisibility.SHARED, + val currentRoomJoinRules: RoomJoinRules = RoomJoinRules.INVITE, + val currentGuestAccess: GuestAccess? = null, val roomSummary: Async = Uninitialized, val isLoading: Boolean = false, val currentRoomAvatarUrl: String? = null, @@ -35,6 +39,7 @@ data class RoomSettingsViewState( val newName: String? = null, val newTopic: String? = null, val newHistoryVisibility: RoomHistoryVisibility? = null, + val newRoomJoinRules: NewJoinRule = NewJoinRule(), val showSaveAction: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions() ) : MvRxState { @@ -45,7 +50,8 @@ data class RoomSettingsViewState( val canChangeAvatar: Boolean = false, val canChangeName: Boolean = false, val canChangeTopic: Boolean = false, - val canChangeHistoryVisibility: Boolean = false + val canChangeHistoryVisibility: Boolean = false, + val canChangeJoinRule: Boolean = false ) sealed class AvatarAction { @@ -54,4 +60,11 @@ data class RoomSettingsViewState( data class UpdateAvatar(val newAvatarUri: Uri, val newAvatarFileName: String) : AvatarAction() } + + data class NewJoinRule( + val newJoinRules: RoomJoinRules? = null, + val newGuestAccess: GuestAccess? = null + ) { + fun hasChanged() = newJoinRules != null || newGuestAccess != null + } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt new file mode 100644 index 0000000000..1c9018d9d1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.joinrule + +import androidx.annotation.DrawableRes +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericAction +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules + +class RoomJoinRuleAction( + val roomJoinRule: RoomJoinRules, + val roomGuestAccess: GuestAccess?, + title: String, + @DrawableRes iconResId: Int, + isSelected: Boolean, + destructive: Boolean +) : BottomSheetGenericAction( + title = title, + iconResId = iconResId, + isSelected = isSelected, + destructive = destructive +) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt new file mode 100644 index 0000000000..4996187029 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleBottomSheet.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.joinrule + +import android.os.Bundle +import android.os.Parcelable +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.ui.bottomsheet.BottomSheetGeneric +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import kotlinx.android.parcel.Parcelize +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import javax.inject.Inject + +@Parcelize +data class RoomJoinRuleBottomSheetArgs( + val currentRoomJoinRule: RoomJoinRules, + val currentGuestAccess: GuestAccess? +) : Parcelable + +class RoomJoinRuleBottomSheet : BottomSheetGeneric() { + + private lateinit var roomJoinRuleSharedActionViewModel: RoomJoinRuleSharedActionViewModel + @Inject lateinit var controller: RoomJoinRuleController + @Inject lateinit var roomJoinRuleViewModelFactory: RoomJoinRuleViewModel.Factory + private val viewModel: RoomJoinRuleViewModel by fragmentViewModel(RoomJoinRuleViewModel::class) + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getController(): BottomSheetGenericController = controller + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + roomJoinRuleSharedActionViewModel = activityViewModelProvider.get(RoomJoinRuleSharedActionViewModel::class.java) + } + + override fun didSelectAction(action: RoomJoinRuleAction) { + roomJoinRuleSharedActionViewModel.post(action) + dismiss() + } + + override fun invalidate() = withState(viewModel) { + controller.setData(it) + super.invalidate() + } + + companion object { + fun newInstance(currentRoomJoinRule: RoomJoinRules, currentGuestAccess: GuestAccess?): RoomJoinRuleBottomSheet { + return RoomJoinRuleBottomSheet().apply { + setArguments(RoomJoinRuleBottomSheetArgs(currentRoomJoinRule, currentGuestAccess)) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt new file mode 100644 index 0000000000..6fd481c91f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.joinrule + +import im.vector.app.R +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import javax.inject.Inject + +class RoomJoinRuleController @Inject constructor( + private val stringProvider: StringProvider +) : BottomSheetGenericController() { + + override fun getActions(state: RoomJoinRuleState): List { + return listOf( + RoomJoinRuleAction( + roomJoinRule = RoomJoinRules.INVITE, + roomGuestAccess = null, + title = stringProvider.getString(R.string.room_settings_room_access_entry_only_invited), + iconResId = 0, + isSelected = state.currentRoomJoinRule == RoomJoinRules.INVITE, + destructive = false + ), + RoomJoinRuleAction( + roomJoinRule = RoomJoinRules.PUBLIC, + roomGuestAccess = GuestAccess.Forbidden, + title = stringProvider.getString(R.string.room_settings_room_access_entry_anyone_with_link_apart_guest), + iconResId = 0, + isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.Forbidden, + destructive = false + ), + RoomJoinRuleAction( + roomJoinRule = RoomJoinRules.PUBLIC, + roomGuestAccess = GuestAccess.CanJoin, + title = stringProvider.getString(R.string.room_settings_room_access_entry_anyone_with_link_including_guest), + iconResId = 0, + isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.CanJoin, + destructive = false + ) + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt new file mode 100644 index 0000000000..934b0dfc76 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleSharedActionViewModel.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.features.roomprofile.settings.joinrule + +import im.vector.app.core.platform.VectorSharedActionViewModel +import javax.inject.Inject + +class RoomJoinRuleSharedActionViewModel @Inject constructor() + : VectorSharedActionViewModel() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt new file mode 100644 index 0000000000..ec16b02d60 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleState.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.joinrule + +import im.vector.app.core.ui.bottomsheet.BottomSheetGenericState +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules + +data class RoomJoinRuleState( + val currentRoomJoinRule: RoomJoinRules = RoomJoinRules.INVITE, + val currentGuestAccess: GuestAccess? = null +) : BottomSheetGenericState() { + + constructor(args: RoomJoinRuleBottomSheetArgs) : this( + currentRoomJoinRule = args.currentRoomJoinRule, + currentGuestAccess = args.currentGuestAccess + ) +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt new file mode 100644 index 0000000000..08d1f8a231 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.settings.joinrule + +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import com.squareup.inject.assisted.Assisted +import com.squareup.inject.assisted.AssistedInject +import im.vector.app.core.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel + +class RoomJoinRuleViewModel @AssistedInject constructor(@Assisted initialState: RoomJoinRuleState) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: RoomJoinRuleState): RoomJoinRuleViewModel + } + + companion object : MvRxViewModelFactory { + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: RoomJoinRuleState): RoomJoinRuleViewModel? { + val fragment: RoomJoinRuleBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.roomJoinRuleViewModelFactory.create(state) + } + } + + override fun handle(action: EmptyAction) { + // No op + } +} diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index c653f58be9..e74b25057b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1023,6 +1023,7 @@ Room History Readability Who can read history? Who can access this room? + Room access Room addresses From 476f721f5eaac9f9ecb10108a6486189fc114e96 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 22:43:51 +0100 Subject: [PATCH 213/231] Cleanup --- .../historyvisibility/RoomHistoryVisibilityAction.kt | 5 ++--- .../historyvisibility/RoomHistoryVisibilityController.kt | 3 +-- .../roomprofile/settings/joinrule/RoomJoinRuleAction.kt | 5 ++--- .../settings/joinrule/RoomJoinRuleController.kt | 9 +++------ 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt index f37964ae23..3c989a7dbe 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityAction.kt @@ -24,11 +24,10 @@ class RoomHistoryVisibilityAction( val roomHistoryVisibility: RoomHistoryVisibility, title: String, @DrawableRes iconResId: Int, - isSelected: Boolean, - destructive: Boolean + isSelected: Boolean ) : BottomSheetGenericAction( title = title, iconResId = iconResId, isSelected = isSelected, - destructive = destructive + destructive = false ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt index 9c9abc03a1..6727b0580c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -37,8 +37,7 @@ class RoomHistoryVisibilityController @Inject constructor( roomHistoryVisibility = roomHistoryVisibility, title = historyVisibilityFormatter.getSetting(roomHistoryVisibility), iconResId = 0, - isSelected = roomHistoryVisibility == state.currentRoomHistoryVisibility, - destructive = false + isSelected = roomHistoryVisibility == state.currentRoomHistoryVisibility ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt index 1c9018d9d1..6f71669002 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleAction.kt @@ -26,11 +26,10 @@ class RoomJoinRuleAction( val roomGuestAccess: GuestAccess?, title: String, @DrawableRes iconResId: Int, - isSelected: Boolean, - destructive: Boolean + isSelected: Boolean ) : BottomSheetGenericAction( title = title, iconResId = iconResId, isSelected = isSelected, - destructive = destructive + destructive = false ) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt index 6fd481c91f..1829707dae 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt @@ -34,24 +34,21 @@ class RoomJoinRuleController @Inject constructor( roomGuestAccess = null, title = stringProvider.getString(R.string.room_settings_room_access_entry_only_invited), iconResId = 0, - isSelected = state.currentRoomJoinRule == RoomJoinRules.INVITE, - destructive = false + isSelected = state.currentRoomJoinRule == RoomJoinRules.INVITE ), RoomJoinRuleAction( roomJoinRule = RoomJoinRules.PUBLIC, roomGuestAccess = GuestAccess.Forbidden, title = stringProvider.getString(R.string.room_settings_room_access_entry_anyone_with_link_apart_guest), iconResId = 0, - isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.Forbidden, - destructive = false + isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.Forbidden ), RoomJoinRuleAction( roomJoinRule = RoomJoinRules.PUBLIC, roomGuestAccess = GuestAccess.CanJoin, title = stringProvider.getString(R.string.room_settings_room_access_entry_anyone_with_link_including_guest), iconResId = 0, - isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.CanJoin, - destructive = false + isSelected = state.currentRoomJoinRule == RoomJoinRules.PUBLIC && state.currentGuestAccess == GuestAccess.CanJoin ) ) } From 589c301606419377f999b207376653884eca52a5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 26 Nov 2020 22:56:37 +0100 Subject: [PATCH 214/231] Fix bad copyright --- .../im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt | 2 +- .../app/core/ui/bottomsheet/BottomSheetGenericController.kt | 2 +- .../ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt | 2 +- .../vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt index d2e1495f1b..da136fb072 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGeneric.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * 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. diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt index 2dfa4fc93a..903c568400 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * 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. diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt index ba8bd4abba..49147b954a 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericSharedActionViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * 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. diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt index b01216afad..38c81a7ef6 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * 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. From 056b9df65e6046ad45b6244025950b79ccdafac3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 08:47:44 +0100 Subject: [PATCH 215/231] Add title to the new two bottom sheets --- .../BottomSheetGenericController.kt | 20 +++++++- .../ui/bottomsheet/BottomSheetTitleItem.kt | 49 +++++++++++++++++++ .../RoomHistoryVisibilityController.kt | 9 +++- .../joinrule/RoomJoinRuleController.kt | 2 + .../res/layout/item_bottom_sheet_title.xml | 40 +++++++++++++++ vector/src/main/res/values/strings.xml | 1 + 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt create mode 100644 vector/src/main/res/layout/item_bottom_sheet_title.xml diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt index 903c568400..a41a389dda 100644 --- a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetGenericController.kt @@ -17,19 +17,37 @@ package im.vector.app.core.ui.bottomsheet import android.view.View import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.core.epoxy.dividerItem /** * Epoxy controller for generic bottom sheet actions */ -abstract class BottomSheetGenericController +abstract class BottomSheetGenericController : TypedEpoxyController() { var listener: Listener? = null + abstract fun getTitle(): String? + + open fun getSubTitle(): String? = null + abstract fun getActions(state: State): List override fun buildModels(state: State?) { state ?: return + // Title + getTitle()?.let { title -> + bottomSheetTitleItem { + id("title") + title(title) + subTitle(getSubTitle()) + } + + dividerItem { + id("title_separator") + } + } + // Actions getActions(state).forEach { action -> action.toBottomSheetItem() .listener(View.OnClickListener { listener?.didSelectAction(action) }) diff --git a/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt new file mode 100644 index 0000000000..27fb634480 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/ui/bottomsheet/BottomSheetTitleItem.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package im.vector.app.core.ui.bottomsheet + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.setTextOrHide + +/** + * A title for bottom sheet, with an optional subtitle. It does not include the bottom separator. + */ +@EpoxyModelClass(layout = R.layout.item_bottom_sheet_title) +abstract class BottomSheetTitleItem : VectorEpoxyModel() { + + @EpoxyAttribute + lateinit var title: String + + @EpoxyAttribute + var subTitle: String? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.title.text = title + holder.subtitle.setTextOrHide(subTitle) + } + + class Holder : VectorEpoxyHolder() { + val title by bind(R.id.itemBottomSheetTitleTitle) + val subtitle by bind(R.id.itemBottomSheetTitleSubtitle) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt index 6727b0580c..a4899711f7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/historyvisibility/RoomHistoryVisibilityController.kt @@ -16,15 +16,22 @@ package im.vector.app.features.roomprofile.settings.historyvisibility +import im.vector.app.R +import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.bottomsheet.BottomSheetGenericController import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import javax.inject.Inject class RoomHistoryVisibilityController @Inject constructor( - private val historyVisibilityFormatter: RoomHistoryVisibilityFormatter + private val historyVisibilityFormatter: RoomHistoryVisibilityFormatter, + private val stringProvider: StringProvider ) : BottomSheetGenericController() { + override fun getTitle() = stringProvider.getString(R.string.room_settings_room_read_history_rules_pref_dialog_title) + + override fun getSubTitle() = stringProvider.getString(R.string.room_settings_room_read_history_dialog_subtitle) + override fun getActions(state: RoomHistoryVisibilityState): List { return listOf( RoomHistoryVisibility.WORLD_READABLE, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt index 1829707dae..ab00396dbe 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/joinrule/RoomJoinRuleController.kt @@ -27,6 +27,8 @@ class RoomJoinRuleController @Inject constructor( private val stringProvider: StringProvider ) : BottomSheetGenericController() { + override fun getTitle() = stringProvider.getString(R.string.room_settings_room_access_rules_pref_dialog_title) + override fun getActions(state: RoomJoinRuleState): List { return listOf( RoomJoinRuleAction( diff --git a/vector/src/main/res/layout/item_bottom_sheet_title.xml b/vector/src/main/res/layout/item_bottom_sheet_title.xml new file mode 100644 index 0000000000..5113c43f39 --- /dev/null +++ b/vector/src/main/res/layout/item_bottom_sheet_title.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index e74b25057b..cff35b962e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1022,6 +1022,7 @@ Room Access Room History Readability Who can read history? + Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged. Who can access this room? Room access From b78f1dbb93003c5ada02d11c0aa25a2d5fb98240 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Nov 2020 08:59:38 +0100 Subject: [PATCH 216/231] Hide the icon area if there is no icon to display --- .../bottomsheet/BottomSheetActionItem.kt | 4 ++ .../BottomSheetGenericController.kt | 5 +- .../res/layout/item_bottom_sheet_action.xml | 53 +++++++++++-------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt index 3666cabce3..80792648f6 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetActionItem.kt @@ -44,6 +44,9 @@ abstract class BottomSheetActionItem : VectorEpoxyModel + val actions = getActions(state) + val showIcons = actions.any { it.iconResId > 0 } + actions.forEach { action -> action.toBottomSheetItem() + .showIcon(showIcons) .listener(View.OnClickListener { listener?.didSelectAction(action) }) .addTo(this) } diff --git a/vector/src/main/res/layout/item_bottom_sheet_action.xml b/vector/src/main/res/layout/item_bottom_sheet_action.xml index 8b5716cd8e..7456f50670 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_action.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_action.xml @@ -13,6 +13,7 @@ android:paddingEnd="@dimen/layout_horizontal_margin" android:paddingBottom="8dp"> + + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_room_actions_notifications_all" /> - + app:layout_constraintStart_toEndOf="@id/actionIcon" + app:layout_constraintTop_toTopOf="parent"> + + + - + tools:ignore="MissingPrefix" + tools:visibility="visible" /> From c785ea63e76f64872a9a61c1a97e6e76b5f0d901 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Nov 2020 17:53:31 +0100 Subject: [PATCH 217/231] Update Change after release --- CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 86258ecc57..6afa782a07 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,9 +3,11 @@ Changes in Element 1.0.12 (2020-XX-XX) Features ✨: - Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428) + - Room setting: update join rules and guest access (#2442) Improvements 🙌: - Add Setting Item to Change PIN (#2462) + - Improve room history visibility setting UX (#1579) Bugfix 🐛: - Double bottomsheet effect after verify with passphrase @@ -32,7 +34,6 @@ Features ✨: - Create DMs with users by scanning their QR code (#2025) - Add Invite friends quick invite actions (#2348) - Add friend by scanning QR code, show your code to friends (#2025) - - Room setting: update join rules and guest access (#2442) Improvements 🙌: - New room creation tile with quick action (#2346) @@ -44,7 +45,6 @@ Improvements 🙌: - Move "Enable Encryption" from room setting screen to room profile screen (#2394) - Home empty screens quick design update (#2347) - Improve Invite user screen (seamless search for matrix ID) - - Improve room history visibility setting UX (#1579) Bugfix 🐛: - Fix crash on AttachmentViewer (#2365) From c6ba2960287d51344b469be9529dc39e165b7a78 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Dec 2020 14:48:10 +0100 Subject: [PATCH 218/231] Create an extension to apply the fix at several places --- .../java/im/vector/app/core/extensions/EditText.kt | 12 ++++++++++++ .../im/vector/app/features/form/FormEditTextItem.kt | 7 ++----- .../app/features/form/FormEditTextWithButtonItem.kt | 5 ++--- .../roomdirectory/createroom/RoomAliasEditItem.kt | 5 ++--- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt index 05b70def3d..33e7199334 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt @@ -57,3 +57,15 @@ fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_searc return@OnTouchListener false }) } + +/** + * Update the edit text value, only if necessary and move the cursor to the end of the text + */ +fun EditText.setTextSafe(value: String?) { + if (value != null && text.toString() != value) { + setText(value) + // To fix jumping cursor to the start https://github.com/airbnb/epoxy/issues/426 + // Note: there is still a known bug if deleting char in the middle of the text, by long pressing on the backspace button. + setSelection(value.length) + } +} diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index eb8945d29c..68e2e6b371 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -26,6 +26,7 @@ import com.google.android.material.textfield.TextInputLayout import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.setTextSafe import im.vector.app.core.platform.SimpleTextWatcher @EpoxyModelClass(layout = R.layout.item_form_text_input) @@ -65,11 +66,7 @@ abstract class FormEditTextItem : VectorEpoxyModel() { holder.textInputLayout.error = errorMessage // Update only if text is different and value is not null - if (value != null && holder.textInputEditText.text.toString() != value) { - holder.textInputEditText.setText(value) - // To fix jumping cursor to the start https://github.com/airbnb/epoxy/issues/426 - holder.textInputEditText.setSelection(value?.length ?: 0) - } + holder.textInputEditText.setTextSafe(value) holder.textInputEditText.isEnabled = enabled inputType?.let { holder.textInputEditText.inputType = it } diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt index eadae3ba0c..08fc435e11 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextWithButtonItem.kt @@ -26,6 +26,7 @@ import com.google.android.material.textfield.TextInputLayout import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.setTextSafe import im.vector.app.core.platform.SimpleTextWatcher @EpoxyModelClass(layout = R.layout.item_form_text_input_with_button) @@ -61,9 +62,7 @@ abstract class FormEditTextWithButtonItem : VectorEpoxyModel() holder.textInputLayout.error = errorMessage // Update only if text is different and value is not null - if (value != null && holder.textInputEditText.text.toString() != value) { - holder.textInputEditText.setText(value) - } + holder.textInputEditText.setTextSafe(value) holder.textInputEditText.isEnabled = enabled holder.textInputEditText.addTextChangedListener(onTextChangeListener) holder.homeServerText.text = homeServer From aa6c7afbbdcc74716ca47e203a2e5e8f2372e5d2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 1 Dec 2020 16:19:50 +0100 Subject: [PATCH 219/231] Remove Status theme and handle migration or current status theme to light theme (#2424) --- CHANGES.md | 2 +- .../res/layout/activity_test_linkify.xml | 2 +- vector/src/debug/res/layout/demo_themes.xml | 10 -- .../features/themes/ActivityOtherThemes.kt | 8 +- .../vector/app/features/themes/ThemeUtils.kt | 18 +-- .../src/main/res/values-v23/theme_status.xml | 11 -- .../src/main/res/values-v27/theme_status.xml | 11 -- vector/src/main/res/values/array.xml | 2 - vector/src/main/res/values/colors_riot.xml | 15 --- vector/src/main/res/values/colors_riotx.xml | 3 - vector/src/main/res/values/styles_riot.xml | 8 -- vector/src/main/res/values/theme_status.xml | 118 ------------------ 12 files changed, 14 insertions(+), 194 deletions(-) delete mode 100644 vector/src/main/res/values-v23/theme_status.xml delete mode 100644 vector/src/main/res/values-v27/theme_status.xml delete mode 100644 vector/src/main/res/values/theme_status.xml diff --git a/CHANGES.md b/CHANGES.md index 6afa782a07..4d3f1c5b9b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,7 +25,7 @@ Test: - Other changes: - - + - Remove "Status.im" theme #2424 Changes in Element 1.0.11 (2020-11-27) =================================================== diff --git a/vector/src/debug/res/layout/activity_test_linkify.xml b/vector/src/debug/res/layout/activity_test_linkify.xml index bbaadb20a2..7e625ad08c 100644 --- a/vector/src/debug/res/layout/activity_test_linkify.xml +++ b/vector/src/debug/res/layout/activity_test_linkify.xml @@ -4,7 +4,7 @@ android:id="@+id/test_linkify_coordinator" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/riot_secondary_text_color_status" + android:background="#7F70808D" tools:context=".features.debug.TestLinkifyActivity"> - - - - - - \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt b/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt index 3aba6a4dad..847caeab4c 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt @@ -24,23 +24,19 @@ import im.vector.app.R * Note that style for light theme is default and is declared in the Android Manifest */ sealed class ActivityOtherThemes(@StyleRes val dark: Int, - @StyleRes val black: Int, - @StyleRes val status: Int) { + @StyleRes val black: Int) { object Default : ActivityOtherThemes( R.style.AppTheme_Dark, - R.style.AppTheme_Black, - R.style.AppTheme_Status + R.style.AppTheme_Black ) object AttachmentsPreview : ActivityOtherThemes( - R.style.AppTheme_AttachmentsPreview, R.style.AppTheme_AttachmentsPreview, R.style.AppTheme_AttachmentsPreview ) object VectorAttachmentsPreview : ActivityOtherThemes( - R.style.AppTheme_Transparent, R.style.AppTheme_Transparent, R.style.AppTheme_Transparent ) diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt index 18faa07954..bba6b9c253 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt @@ -24,6 +24,7 @@ import android.view.Menu import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.core.content.ContextCompat +import androidx.core.content.edit import androidx.core.graphics.drawable.DrawableCompat import im.vector.app.R import im.vector.app.core.di.DefaultSharedPreferences @@ -41,7 +42,6 @@ object ThemeUtils { private const val THEME_DARK_VALUE = "dark" private const val THEME_LIGHT_VALUE = "light" private const val THEME_BLACK_VALUE = "black" - private const val THEME_STATUS_VALUE = "status" private var currentTheme = AtomicReference(null) @@ -58,9 +58,8 @@ object ThemeUtils { */ fun isLightTheme(context: Context): Boolean { return when (getApplicationTheme(context)) { - THEME_LIGHT_VALUE, - THEME_STATUS_VALUE -> true - else -> false + THEME_LIGHT_VALUE -> true + else -> false } } @@ -73,8 +72,13 @@ object ThemeUtils { fun getApplicationTheme(context: Context): String { val currentTheme = this.currentTheme.get() return if (currentTheme == null) { - val themeFromPref = DefaultSharedPreferences.getInstance(context) - .getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) ?: THEME_LIGHT_VALUE + val prefs = DefaultSharedPreferences.getInstance(context) + var themeFromPref = prefs.getString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) ?: THEME_LIGHT_VALUE + if (themeFromPref == "status") { + // Migrate to light theme, which is the closest theme + themeFromPref = THEME_LIGHT_VALUE + prefs.edit { putString(APPLICATION_THEME_KEY, THEME_LIGHT_VALUE) } + } this.currentTheme.set(themeFromPref) themeFromPref } else { @@ -92,7 +96,6 @@ object ThemeUtils { when (aTheme) { THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark) THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black) - THEME_STATUS_VALUE -> context.setTheme(R.style.AppTheme_Status) else -> context.setTheme(R.style.AppTheme_Light) } @@ -109,7 +112,6 @@ object ThemeUtils { when (getApplicationTheme(activity)) { THEME_DARK_VALUE -> activity.setTheme(otherThemes.dark) THEME_BLACK_VALUE -> activity.setTheme(otherThemes.black) - THEME_STATUS_VALUE -> activity.setTheme(otherThemes.status) } mColorByAttr.clear() diff --git a/vector/src/main/res/values-v23/theme_status.xml b/vector/src/main/res/values-v23/theme_status.xml deleted file mode 100644 index 236864d4b8..0000000000 --- a/vector/src/main/res/values-v23/theme_status.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - -