From 5338f93852101b2cef3a7a457e781fad86a3ae10 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Dec 2019 19:52:12 +0100 Subject: [PATCH 01/61] Sync: use a foreground service for initialSync. --- .../matrix/android/api/failure/Failure.kt | 2 + .../matrix/android/api/session/Session.kt | 5 + .../android/api/session/cache/CacheService.kt | 2 +- .../network/NetworkConnectivityChecker.kt | 29 +++- .../internal/session/DefaultSession.kt | 18 +-- .../internal/session/SessionComponent.kt | 3 + .../android/internal/session/sync/SyncTask.kt | 4 + .../internal/session/sync/job/SyncService.kt | 134 ++++++------------ .../internal/session/sync/job/SyncThread.kt | 3 +- .../internal/session/sync/job/SyncWorker.kt | 42 ++++-- vector/src/fdroid/AndroidManifest.xml | 4 - .../receiver/AlarmSyncBroadcastReceiver.kt | 12 +- vector/src/main/AndroidManifest.xml | 4 + .../java/im/vector/riotx/VectorApplication.kt | 3 +- .../vector/riotx/core/extensions/Session.kt | 29 +++- .../riotx/core/services}/VectorSyncService.kt | 15 +- .../im/vector/riotx/features/MainActivity.kt | 12 +- .../riotx/features/login/LoginViewModel.kt | 4 +- 18 files changed, 177 insertions(+), 148 deletions(-) rename vector/src/{fdroid/java/im/vector/riotx/fdroid/service => main/java/im/vector/riotx/core/services}/VectorSyncService.kt (83%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 4d44e3346b..41468e9e0f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -44,3 +44,5 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { abstract class FeatureFailure : Failure() } + +fun Throwable.isTokenError() = this is Failure.ServerError && (this.error.code == MatrixError.UNKNOWN_TOKEN || this.error.code == MatrixError.MISSING_TOKEN) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 2440713a40..5fd7e3885f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -104,6 +104,11 @@ interface Session : */ fun syncState(): LiveData + /** + * This methods return true if an initial sync has been processed + */ + fun hasAlreadySynced(): Boolean + /** * This method allow to close a session. It does stop some services. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt index a84e5af48c..2f34922280 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.api.MatrixCallback interface CacheService { /** - * Clear the whole cached data, except credentials. Once done, the session is closed and has to be opened again + * Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user. */ fun clearCache(callback: MatrixCallback) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt index 3d850c223a..4d4699810f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.network import android.content.Context +import androidx.annotation.WorkerThread import com.novoda.merlin.Merlin import com.novoda.merlin.MerlinsBeard import im.vector.matrix.android.internal.di.MatrixScope @@ -28,8 +29,8 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @MatrixScope -internal class NetworkConnectivityChecker @Inject constructor(context: Context, - backgroundDetectionObserver: BackgroundDetectionObserver) +internal class NetworkConnectivityChecker @Inject constructor(private val context: Context, + private val backgroundDetectionObserver: BackgroundDetectionObserver) : BackgroundDetectionObserver.Listener { private val merlin = Merlin.Builder() @@ -37,19 +38,30 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context, .withDisconnectableCallbacks() .build(context) - private val listeners = Collections.synchronizedSet(LinkedHashSet()) + private val merlinsBeard = MerlinsBeard.Builder().build(context) - // True when internet is available - var hasInternetAccess = MerlinsBeard.Builder().build(context).isConnected - private set + private val listeners = Collections.synchronizedSet(LinkedHashSet()) + private var hasInternetAccess = merlinsBeard.isConnected init { backgroundDetectionObserver.register(this) } + /** + * Returns true when internet is available + */ + @WorkerThread + fun hasInternetAccess(): Boolean { + // If we are in background we have unbound merlin, so we have to check + return if (backgroundDetectionObserver.isIsBackground) { + merlinsBeard.hasInternetAccess() + } else { + hasInternetAccess + } + } + override fun onMoveToForeground() { merlin.bind() - merlin.registerDisconnectable { if (hasInternetAccess) { Timber.v("On Disconnect") @@ -76,14 +88,17 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context, merlin.unbind() } + // In background you won't get notification as merlin is unbound suspend fun waitUntilConnected() { if (hasInternetAccess) { return } else { + Timber.v("Waiting for network...") suspendCoroutine { continuation -> register(object : Listener { override fun onConnect() { unregister(this) + Timber.v("Connected to network...") continuation.resume(Unit) } }) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 4b33e28000..32126fb78d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -44,6 +44,7 @@ import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.database.LiveEntityObserver +import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncWorker import org.greenrobot.eventbus.EventBus @@ -72,6 +73,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se private val secureStorageService: Lazy, private val syncThreadProvider: Provider, private val contentUrlResolver: ContentUrlResolver, + private val syncTokenStore: SyncTokenStore, private val contentUploadProgressTracker: ContentUploadStateTracker, private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy) @@ -147,6 +149,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se return getSyncThread().liveState() } + override fun hasAlreadySynced(): Boolean { + return syncTokenStore.getLastToken() != null + } + private fun getSyncThread(): SyncThread { return syncThread ?: syncThreadProvider.get().also { syncThread = it @@ -156,17 +162,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se override fun clearCache(callback: MatrixCallback) { stopSync() stopAnyBackgroundSync() - cacheService.get().clearCache(object : MatrixCallback { - override fun onSuccess(data: Unit) { - startSync(true) - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - startSync(true) - callback.onFailure(failure) - } - }) + cacheService.get().clearCache(callback) } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index 8c16050442..a208a7a720 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -46,6 +46,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.user.UserModule import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @Component(dependencies = [MatrixComponent::class], modules = [ @@ -69,6 +70,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor @SessionScope internal interface SessionComponent { + fun coroutineDispatchers(): MatrixCoroutineDispatchers + fun session(): Session fun syncTask(): SyncTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 51c02456d7..1082fa835b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.filter.FilterRepository @@ -28,6 +29,7 @@ import im.vector.matrix.android.internal.session.homeserver.GetHomeServerCapabil import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.user.UserStore import im.vector.matrix.android.internal.task.Task +import timber.log.Timber import javax.inject.Inject internal interface SyncTask : Task { @@ -47,6 +49,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, ) : SyncTask { override suspend fun execute(params: SyncTask.Params) { + Timber.v("Sync task started on Thread: ${Thread.currentThread().name}") // Maybe refresh the home server capabilities data we know getHomeServerCapabilitiesTask.execute(Unit) @@ -84,5 +87,6 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, if (isInitialSync) { initialSyncProgressService.endAll() } + Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}") } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 4e57aa5be1..3aeb408db1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -18,21 +18,15 @@ package im.vector.matrix.android.internal.session.sync.job import android.app.Service import android.content.Intent import android.os.IBinder -import com.squareup.moshi.JsonEncodingException import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.failure.MatrixError -import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.api.failure.isTokenError import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.TaskThread -import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.* import timber.log.Timber -import java.net.SocketTimeoutException -import java.util.Timer -import java.util.TimerTask +import java.util.concurrent.atomic.AtomicBoolean /** * Can execute periodic sync task. @@ -43,13 +37,15 @@ import java.util.TimerTask open class SyncService : Service() { private var mIsSelfDestroyed: Boolean = false - private var cancelableTask: Cancelable? = null private lateinit var syncTask: SyncTask private lateinit var networkConnectivityChecker: NetworkConnectivityChecker private lateinit var taskExecutor: TaskExecutor + private lateinit var coroutineDispatchers: MatrixCoroutineDispatchers - var timer = Timer() + private val isRunning = AtomicBoolean(false) + + private val serviceScope = CoroutineScope(SupervisorJob()) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.i("onStartCommand $intent") @@ -60,13 +56,14 @@ open class SyncService : Service() { syncTask = sessionComponent.syncTask() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() - if (cancelableTask == null) { - timer.cancel() - timer = Timer() - doSync(true) + coroutineDispatchers = sessionComponent.coroutineDispatchers() + if (isRunning.get()) { + Timber.i("Received a start while was already syncing... ignore") } else { - // Already syncing ignore - Timber.i("Received a start while was already syncking... ignore") + isRunning.set(true) + serviceScope.launch(coroutineDispatchers.sync) { + doSync() + } } } // No intent just start the service, an alarm will should call with intent @@ -75,86 +72,40 @@ open class SyncService : Service() { override fun onDestroy() { Timber.i("## onDestroy() : $this") - if (!mIsSelfDestroyed) { Timber.w("## Destroy by the system : $this") } - - cancelableTask?.cancel() + serviceScope.coroutineContext.cancelChildren() + isRunning.set(false) super.onDestroy() } - fun stopMe() { - timer.cancel() - timer = Timer() - cancelableTask?.cancel() + private fun stopMe() { mIsSelfDestroyed = true stopSelf() } - fun doSync(once: Boolean = false) { - if (!networkConnectivityChecker.hasInternetAccess) { - Timber.v("No internet access. Waiting...") - // TODO Retry in ? - timer.schedule(object : TimerTask() { - override fun run() { - doSync() - } - }, NO_NETWORK_DELAY) - } else { - Timber.v("Execute sync request with timeout 0") - val params = SyncTask.Params(TIME_OUT) - cancelableTask = syncTask - .configureWith(params) { - callbackThread = TaskThread.SYNC - executionThread = TaskThread.SYNC - callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - cancelableTask = null - if (!once) { - timer.schedule(object : TimerTask() { - override fun run() { - doSync() - } - }, NEXT_BATCH_DELAY) - } else { - // stop - stopMe() - } - } - - override fun onFailure(failure: Throwable) { - Timber.e(failure) - cancelableTask = null - if (failure is Failure.NetworkConnection - && failure.cause is SocketTimeoutException) { - // Timeout are not critical - timer.schedule(object : TimerTask() { - override fun run() { - doSync() - } - }, 5_000L) - } - - if (failure !is Failure.NetworkConnection - || failure.cause is JsonEncodingException) { - // Wait 10s before retrying - timer.schedule(object : TimerTask() { - override fun run() { - doSync() - } - }, 5_000L) - } - - if (failure is Failure.ServerError - && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) { - // No token or invalid token, stop the thread - stopSelf() - } - } - } - } - .executeBy(taskExecutor) + private suspend fun doSync() { + if (!networkConnectivityChecker.hasInternetAccess()) { + Timber.v("No network, try to sync again in 10s") + delay(DELAY_NO_NETWORK) + doSync() + return + } + Timber.v("Execute sync request with timeout 0") + val params = SyncTask.Params(TIME_OUT) + try { + syncTask.execute(params) + stopMe() + } catch (throwable: Throwable) { + Timber.e(throwable) + if (throwable.isTokenError()) { + stopMe() + } else { + Timber.v("Retry to sync in 5s") + delay(DELAY_FAILURE) + doSync() + } } } @@ -164,9 +115,8 @@ open class SyncService : Service() { companion object { const val EXTRA_USER_ID = "EXTRA_USER_ID" - - const val TIME_OUT = 0L - const val NEXT_BATCH_DELAY = 60_000L - const val NO_NETWORK_DELAY = 5_000L + private const val TIME_OUT = 0L + private const val DELAY_NO_NETWORK = 10_000L + private const val DELAY_FAILURE = 5_000L } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index d8de292d70..67169cdc3c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -99,11 +99,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, isStarted = true networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) - while (state != SyncState.KILLING) { Timber.v("Entering loop, state: $state") - if (!networkConnectivityChecker.hasInternetAccess) { + if (!networkConnectivityChecker.hasInternetAccess()) { Timber.v("No network. Waiting...") updateStateTo(SyncState.NO_NETWORK) synchronized(lock) { lock.wait() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index b5177172d0..804102913d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.session.sync.job import android.content.Context import androidx.work.* import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.api.failure.MatrixError +import im.vector.matrix.android.api.failure.isTokenError +import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -25,6 +29,7 @@ import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import timber.log.Timber import java.util.concurrent.TimeUnit @@ -46,45 +51,58 @@ internal class SyncWorker(context: Context, @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor @Inject lateinit var coroutineDispatchers: MatrixCoroutineDispatchers + @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker override suspend fun doWork(): Result { Timber.i("Sync work starting") val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) - runCatching { - withContext(coroutineDispatchers.sync) { - val taskParams = SyncTask.Params(0) - syncTask.execute(taskParams) - } - } - return Result.success() + return runCatching { + doSync(params.timeout) + }.fold( + { Result.success() }, + { failure -> + if (failure.isTokenError() || !params.automaticallyRetry) { + Result.failure() + } else { + Result.retry() + } + } + ) + } + + private suspend fun doSync(timeout: Long) = withContext(coroutineDispatchers.sync) { + val taskParams = SyncTask.Params(timeout) + syncTask.execute(taskParams) } companion object { + const val BG_SYNC_WORK_NAME = "BG_SYNCP" + fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) val workRequest = matrixOneTimeWorkRequestBuilder() - .setInputData(data) .setConstraints(WorkManagerUtil.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .setInputData(data) .build() - WorkManager.getInstance(context).enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) + WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) } fun automaticallyBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0, delay: Long = 30_000) { val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true)) val workRequest = matrixOneTimeWorkRequestBuilder() - .setInputData(data) .setConstraints(WorkManagerUtil.workConstraints) + .setInputData(data) .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) .build() - WorkManager.getInstance(context).enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) + WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) } fun stopAnyBackgroundSync(context: Context) { - WorkManager.getInstance(context).cancelUniqueWork("BG_SYNCP") + WorkManager.getInstance(context).cancelUniqueWork(BG_SYNC_WORK_NAME) } } } diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 1a35caff41..51234e0bba 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -20,10 +20,6 @@ android:enabled="true" android:exported="false" /> - - \ No newline at end of file diff --git a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 6648d79490..951fcaa14d 100644 --- a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -25,7 +25,7 @@ import android.os.Build import android.os.PowerManager import androidx.core.content.ContextCompat import im.vector.matrix.android.internal.session.sync.job.SyncService -import im.vector.riotx.fdroid.service.VectorSyncService +import im.vector.riotx.core.services.VectorSyncService import timber.log.Timber class AlarmSyncBroadcastReceiver : BroadcastReceiver() { @@ -41,14 +41,9 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { val userId = intent.getStringExtra(SyncService.EXTRA_USER_ID) // This method is called when the BroadcastReceiver is receiving an Intent broadcast. Timber.d("RestartBroadcastReceiver received intent") - Intent(context, VectorSyncService::class.java).also { - it.putExtra(SyncService.EXTRA_USER_ID, userId) + VectorSyncService.newIntent(context, userId).also { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ContextCompat.startForegroundService(context, it) - } else { - context.startService(it) - } + ContextCompat.startForegroundService(context, it) } catch (ex: Throwable) { // TODO Timber.e(ex) @@ -79,6 +74,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } fun cancelAlarm(context: Context) { + Timber.v("Cancel alarm") val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 5f1687c9c9..49af440501 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -104,6 +104,10 @@ android:name=".core.services.CallService" android:exported="false" /> + + diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index b6b0d16360..3f55e8f5e6 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -116,11 +116,12 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! activeSessionHolder.setActiveSession(lastAuthenticatedSession) - lastAuthenticatedSession.configureAndStart(pushRuleTriggerListener, sessionListener) + lastAuthenticatedSession.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener) } ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { + Timber.i("App entered foreground") FcmHelper.onEnterForeground(appContext) activeSessionHolder.getSafeActiveSession()?.also { it.stopAnyBackgroundSync() diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index 0ce4d04497..9a6905e4c7 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -16,23 +16,27 @@ package im.vector.riotx.core.extensions +import android.content.Context +import android.content.Intent +import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.sync.FilterService +import im.vector.matrix.android.internal.session.sync.job.SyncService +import im.vector.riotx.core.services.VectorSyncService import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener import timber.log.Timber -fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener, +fun Session.configureAndStart(context: Context, + pushRuleTriggerListener: PushRuleTriggerListener, sessionListener: SessionListener) { open() addListener(sessionListener) setFilter(FilterService.FilterPreset.RiotFilter) Timber.i("Configure and start session for ${this.myUserId}") - val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) - Timber.v("--> is at least started? $isAtLeastStarted") - startSync(isAtLeastStarted) + startSyncing(context) refreshPushers() pushRuleTriggerListener.startWithSession(this) @@ -40,3 +44,20 @@ fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener, // @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler // @Inject lateinit var keyRequestHandler: KeyRequestHandler } + +fun Session.startSyncing(context: Context) { + val applicationContext = context.applicationContext + if (!hasAlreadySynced()) { + VectorSyncService.newIntent(applicationContext, myUserId).also { + try { + ContextCompat.startForegroundService(applicationContext, it) + } catch (ex: Throwable) { + // TODO + Timber.e(ex) + } + } + } + val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) + Timber.v("--> is at least started? $isAtLeastStarted") + startSync(isAtLeastStarted) +} diff --git a/vector/src/fdroid/java/im/vector/riotx/fdroid/service/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt similarity index 83% rename from vector/src/fdroid/java/im/vector/riotx/fdroid/service/VectorSyncService.kt rename to vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index fbf9c1a031..175f921e07 100644 --- a/vector/src/fdroid/java/im/vector/riotx/fdroid/service/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.fdroid.service +package im.vector.riotx.core.services import android.app.NotificationManager import android.content.Context import android.content.Intent import android.os.Build +import androidx.core.content.ContextCompat import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.riotx.R import im.vector.riotx.core.extensions.vectorComponent @@ -27,6 +28,15 @@ import timber.log.Timber class VectorSyncService : SyncService() { + companion object { + + fun newIntent(context: Context, userId: String): Intent { + return Intent(context, VectorSyncService::class.java).also { + it.putExtra(EXTRA_USER_ID, userId) + } + } + } + private lateinit var notificationUtils: NotificationUtils override fun onCreate() { @@ -45,8 +55,7 @@ class VectorSyncService : SyncService() { } /** - * Service is started only in fdroid mode when no FCM is available - * Otherwise it is bounded + * Service is started in fdroid mode when no FCM is available or is used for initialSync */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.v("VectorSyncService - onStartCommand ") diff --git a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt index 7064ad0d49..b95eddd011 100644 --- a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt @@ -26,6 +26,8 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.extensions.configureAndStart +import im.vector.riotx.core.extensions.startSyncing import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.utils.deleteAllFiles import im.vector.riotx.features.home.HomeActivity @@ -75,8 +77,13 @@ class MainActivity : VectorBaseActivity() { } private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) { + val session = sessionHolder.getSafeActiveSession() + if (session == null) { + start() + return + } when { - clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback { + clearCredentials -> session.signOut(object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.w("SIGN_OUT: success, start app") sessionHolder.clearActiveSession() @@ -87,8 +94,9 @@ class MainActivity : VectorBaseActivity() { displayError(failure, clearCache, clearCredentials) } }) - clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback { + clearCache -> session.clearCache(object : MatrixCallback { override fun onSuccess(data: Unit) { + session.startSyncing(applicationContext) doLocalCleanupAndStart() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 00207cbfbf..11f2f149a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.login +import android.content.Context import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -44,6 +45,7 @@ import java.util.concurrent.CancellationException * */ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState, + private val applicationContext: Context, private val authenticationService: AuthenticationService, private val activeSessionHolder: ActiveSessionHolder, private val pushRuleTriggerListener: PushRuleTriggerListener, @@ -469,7 +471,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private fun onSessionCreated(session: Session) { activeSessionHolder.setActiveSession(session) - session.configureAndStart(pushRuleTriggerListener, sessionListener) + session.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener) setState { copy( asyncLoginAction = Success(Unit) From 3a269be2ef253e6f1ba9611baf657729a82617d1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Dec 2019 16:24:30 +0100 Subject: [PATCH 02/61] Sync: fix crash on gplay flavor and reschedule when no network instead of showing a potential notification all the time --- .../network/NetworkConnectivityChecker.kt | 3 ++ .../internal/session/sync/job/SyncService.kt | 19 +++++---- vector/src/gplay/AndroidManifest.xml | 2 + .../riotx/core/services/VectorSyncService.kt | 42 ++++++++++++++----- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt index 4d4699810f..e6dda3f5e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt @@ -62,6 +62,9 @@ internal class NetworkConnectivityChecker @Inject constructor(private val contex override fun onMoveToForeground() { merlin.bind() + merlinsBeard.hasInternetAccess { + hasInternetAccess = it + } merlin.registerDisconnectable { if (hasInternetAccess) { Timber.v("On Disconnect") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 3aeb408db1..8a91186e07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -34,8 +34,9 @@ import java.util.concurrent.atomic.AtomicBoolean * in order to be able to perform a sync even if the app is not running. * The and must be declared in the Manifest or the app using the SDK */ -open class SyncService : Service() { +abstract class SyncService : Service() { + private var userId: String? = null private var mIsSelfDestroyed: Boolean = false private lateinit var syncTask: SyncTask @@ -50,9 +51,10 @@ open class SyncService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.i("onStartCommand $intent") intent?.let { - val userId = it.getStringExtra(EXTRA_USER_ID) - val sessionComponent = Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(userId) + val safeUserId = it.getStringExtra(EXTRA_USER_ID) ?: return@let + val sessionComponent = Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(safeUserId) ?: return@let + userId = safeUserId syncTask = sessionComponent.syncTask() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() @@ -87,9 +89,11 @@ open class SyncService : Service() { private suspend fun doSync() { if (!networkConnectivityChecker.hasInternetAccess()) { - Timber.v("No network, try to sync again in 10s") - delay(DELAY_NO_NETWORK) - doSync() + Timber.v("No network reschedule to avoid wasting resources") + userId?.also { + onRescheduleAsked(it, delay = 10_000L) + } + stopMe() return } Timber.v("Execute sync request with timeout 0") @@ -109,6 +113,8 @@ open class SyncService : Service() { } } + abstract fun onRescheduleAsked(userId: String, delay: Long) + override fun onBind(intent: Intent?): IBinder? { return null } @@ -116,7 +122,6 @@ open class SyncService : Service() { companion object { const val EXTRA_USER_ID = "EXTRA_USER_ID" private const val TIME_OUT = 0L - private const val DELAY_NO_NETWORK = 10_000L private const val DELAY_FAILURE = 5_000L } } diff --git a/vector/src/gplay/AndroidManifest.xml b/vector/src/gplay/AndroidManifest.xml index db30f21f7a..85567df2c1 100755 --- a/vector/src/gplay/AndroidManifest.xml +++ b/vector/src/gplay/AndroidManifest.xml @@ -2,6 +2,8 @@ + + diff --git a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index 175f921e07..93c08c50d6 100644 --- a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -15,11 +15,12 @@ */ package im.vector.riotx.core.services +import android.app.AlarmManager import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build -import androidx.core.content.ContextCompat import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.riotx.R import im.vector.riotx.core.extensions.vectorComponent @@ -44,16 +45,6 @@ class VectorSyncService : SyncService() { notificationUtils = vectorComponent().notificationUtils() } - override fun onDestroy() { - removeForegroundNotif() - super.onDestroy() - } - - private fun removeForegroundNotif() { - val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) - } - /** * Service is started in fdroid mode when no FCM is available or is used for initialSync */ @@ -65,4 +56,33 @@ class VectorSyncService : SyncService() { } return super.onStartCommand(intent, flags, startId) } + + override fun onDestroy() { + removeForegroundNotif() + super.onDestroy() + } + + override fun onRescheduleAsked(userId: String, delay: Long) { + reschedule(userId, delay) + } + + private fun removeForegroundNotif() { + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) + } + + private fun reschedule(userId: String, delay: Long) { + val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PendingIntent.getForegroundService(this, 0, newIntent(this, userId), 0) + } else { + PendingIntent.getService(this, 0, newIntent(this, userId), 0) + } + val firstMillis = System.currentTimeMillis() + delay + val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) + } else { + alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) + } + } } From 6b61c958432254c752d1bd7e18f3390899861b19 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 11 Dec 2019 20:39:07 +0100 Subject: [PATCH 03/61] Coroutines: introduce a sequencer --- .../internal/session/sync/job/SyncThread.kt | 83 +++++++---------- .../internal/task/CoroutineSequencer.kt | 93 +++++++++++++++++++ .../task/MatrixCoroutineSequencersTest.kt | 61 ++++++++++++ 3 files changed, 190 insertions(+), 47 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt create mode 100644 matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 67169cdc3c..01923b103f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -30,6 +30,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.BackgroundDetectionObserver +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking import timber.log.Timber import java.net.SocketTimeoutException import java.util.concurrent.CountDownLatch @@ -99,9 +101,9 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, isStarted = true networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) + while (state != SyncState.KILLING) { Timber.v("Entering loop, state: $state") - if (!networkConnectivityChecker.hasInternetAccess()) { Timber.v("No network. Waiting...") updateStateTo(SyncState.NO_NETWORK) @@ -116,57 +118,13 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, if (state !is SyncState.RUNNING) { updateStateTo(SyncState.RUNNING(afterPause = true)) } - // No timeout after a pause val timeout = state.let { if (it is SyncState.RUNNING && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT } - Timber.v("Execute sync request with timeout $timeout") - val latch = CountDownLatch(1) val params = SyncTask.Params(timeout) - - cancelableTask = syncTask.configureWith(params) { - this.callbackThread = TaskThread.SYNC - this.executionThread = TaskThread.SYNC - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.v("onSuccess") - latch.countDown() - } - - override fun onFailure(failure: Throwable) { - if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) { - // Timeout are not critical - Timber.v("Timeout") - } else if (failure is Failure.Cancelled) { - Timber.v("Cancelled") - } else if (failure is Failure.ServerError - && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) { - // No token or invalid token, stop the thread - Timber.w(failure) - updateStateTo(SyncState.KILLING) - } else { - Timber.e(failure) - - if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) { - // Wait 10s before retrying - Timber.v("Wait 10s") - sleep(RETRY_WAIT_TIME_MS) - } - } - - latch.countDown() - } - } + runBlocking { + doSync(params) } - .executeBy(taskExecutor) - - latch.await() - state.let { - if (it is SyncState.RUNNING && it.afterPause) { - updateStateTo(SyncState.RUNNING(afterPause = false)) - } - } - Timber.v("...Continue") } } @@ -176,6 +134,37 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, networkConnectivityChecker.unregister(this) } + private suspend fun doSync(params: SyncTask.Params) { + try { + syncTask.execute(params) + } catch (failure: Throwable) { + if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) { + // Timeout are not critical + Timber.v("Timeout") + } else if (failure is Failure.Cancelled) { + Timber.v("Cancelled") + } else if (failure is Failure.ServerError + && (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) { + // No token or invalid token, stop the thread + Timber.w(failure) + updateStateTo(SyncState.KILLING) + } else { + Timber.e(failure) + if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) { + // Wait 10s before retrying + Timber.v("Wait 10s") + delay(RETRY_WAIT_TIME_MS) + } + } + } finally { + state.let { + if (it is SyncState.RUNNING && it.afterPause) { + updateStateTo(SyncState.RUNNING(afterPause = false)) + } + } + } + } + private fun updateStateTo(newState: SyncState) { Timber.v("Update state from $state to $newState") state = newState diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt new file mode 100644 index 0000000000..c0802f9fae --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt @@ -0,0 +1,93 @@ +/* + * 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.matrix.android.internal.task + +import im.vector.matrix.android.internal.di.MatrixScope +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import java.util.concurrent.Executors +import javax.inject.Inject + + +@MatrixScope +internal class MatrixCoroutineSequencers @Inject constructor() { + + private val sequencers = HashMap() + + suspend fun post(name: String, block: suspend CoroutineScope.() -> Any): Any { + val sequencer = sequencers.getOrPut(name) { + ChannelCoroutineSequencer() + } + return sequencer.post(block) + } + + fun cancel(name: String) { + sequencers.remove(name)?.cancel() + } + + fun cancelAll() { + sequencers.values.forEach { + it.cancel() + } + sequencers.clear() + } + +} + +internal interface CoroutineSequencer { + suspend fun post(block: suspend CoroutineScope.() -> Any): Any + fun cancel() +} + +internal class ChannelCoroutineSequencer : CoroutineSequencer { + + private data class Message( + val block: suspend CoroutineScope.() -> Any, + val deferred: CompletableDeferred + ) + + private val messageChannel: Channel = Channel() + private val coroutineScope = CoroutineScope(SupervisorJob()) + private val singleDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + init { + coroutineScope.launch(singleDispatcher) { + for (message in messageChannel) { + try { + val result = message.block(this) + message.deferred.complete(result) + } catch (exception: Throwable) { + message.deferred.completeExceptionally(exception) + } + } + } + } + + override fun cancel() { + messageChannel.cancel() + coroutineScope.coroutineContext.cancelChildren() + } + + override suspend fun post(block: suspend CoroutineScope.() -> Any): Any { + val deferred = CompletableDeferred() + val message = Message(block, deferred) + messageChannel.send(message) + return deferred.await() + } + +} + diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt new file mode 100644 index 0000000000..10cfd92fc5 --- /dev/null +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.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.matrix.android.internal.task + +import kotlinx.coroutines.* +import org.junit.Test +import java.util.concurrent.Executors + +class MatrixCoroutineSequencersTest { + + @Test + fun sequencer_should_run_sequential() { + val sequencer = MatrixCoroutineSequencers() + val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + + val jobs = listOf( + GlobalScope.launch(dispatcher) { + sequencer.post("Sequencer1") { suspendingMethod("#3") } + }, + GlobalScope.launch(dispatcher) { + sequencer.post("Sequencer1") { suspendingMethod("#4") } + }, + GlobalScope.launch(dispatcher) { + sequencer.post("Sequencer2") { suspendingMethod("#5") } + }, + GlobalScope.launch(dispatcher) { + sequencer.post("Sequencer2") { suspendingMethod("#6") } + }, + GlobalScope.launch(dispatcher) { + sequencer.post("Sequencer2") { suspendingMethod("#7") } + } + ) + Thread.sleep(5500) + sequencer.cancelAll() + runBlocking { + jobs.joinAll() + } + } + + private suspend fun suspendingMethod(name: String): String = withContext(Dispatchers.Default) { + println("BLOCKING METHOD $name STARTS") + delay(3000) + println("BLOCKING METHOD $name ENDS") + name + } + +} From eab94b4f03cc220a00a91907432db8eefad2cb7a Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 12 Dec 2019 14:30:40 +0100 Subject: [PATCH 04/61] Sequencer: handle cancellation --- .../internal/task/CoroutineSequencer.kt | 77 ++++++------- .../task/MatrixCoroutineSequencersTest.kt | 108 +++++++++++++++--- 2 files changed, 125 insertions(+), 60 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt index c0802f9fae..0f8439b144 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt @@ -16,59 +16,43 @@ package im.vector.matrix.android.internal.task -import im.vector.matrix.android.internal.di.MatrixScope -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.intrinsics.startCoroutineCancellable +import kotlinx.coroutines.launch import java.util.concurrent.Executors -import javax.inject.Inject - -@MatrixScope -internal class MatrixCoroutineSequencers @Inject constructor() { - - private val sequencers = HashMap() - - suspend fun post(name: String, block: suspend CoroutineScope.() -> Any): Any { - val sequencer = sequencers.getOrPut(name) { - ChannelCoroutineSequencer() - } - return sequencer.post(block) - } - - fun cancel(name: String) { - sequencers.remove(name)?.cancel() - } - - fun cancelAll() { - sequencers.values.forEach { - it.cancel() - } - sequencers.clear() - } - -} - -internal interface CoroutineSequencer { - suspend fun post(block: suspend CoroutineScope.() -> Any): Any +internal interface CoroutineSequencer { + suspend fun post(block: suspend () -> T): T fun cancel() + fun close() } -internal class ChannelCoroutineSequencer : CoroutineSequencer { +internal class ChannelCoroutineSequencer : CoroutineSequencer { - private data class Message( - val block: suspend CoroutineScope.() -> Any, - val deferred: CompletableDeferred + private data class Message( + val block: suspend () -> T, + val deferred: CompletableDeferred ) - private val messageChannel: Channel = Channel() + private val messageChannel: Channel> = Channel() private val coroutineScope = CoroutineScope(SupervisorJob()) private val singleDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() init { + launchCoroutine() + } + + private fun launchCoroutine() { coroutineScope.launch(singleDispatcher) { for (message in messageChannel) { try { - val result = message.block(this) + val result = message.block() message.deferred.complete(result) } catch (exception: Throwable) { message.deferred.completeExceptionally(exception) @@ -77,16 +61,27 @@ internal class ChannelCoroutineSequencer : CoroutineSequencer { } } - override fun cancel() { + override fun close() { messageChannel.cancel() coroutineScope.coroutineContext.cancelChildren() } - override suspend fun post(block: suspend CoroutineScope.() -> Any): Any { - val deferred = CompletableDeferred() + override fun cancel() { + close() + launchCoroutine() + } + + override suspend fun post(block: suspend () -> T): T { + val deferred = CompletableDeferred() val message = Message(block, deferred) messageChannel.send(message) - return deferred.await() + return try { + deferred.await() + } catch (cancellation: CancellationException) { + coroutineScope.coroutineContext.cancelChildren() + launchCoroutine() + throw cancellation + } } } diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt index 10cfd92fc5..c69146c3dc 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt @@ -16,46 +16,116 @@ package im.vector.matrix.android.internal.task -import kotlinx.coroutines.* +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.delay +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals import org.junit.Test import java.util.concurrent.Executors class MatrixCoroutineSequencersTest { + private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + @Test fun sequencer_should_run_sequential() { - val sequencer = MatrixCoroutineSequencers() - val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + val sequencer = ChannelCoroutineSequencer() + val results = ArrayList() val jobs = listOf( GlobalScope.launch(dispatcher) { - sequencer.post("Sequencer1") { suspendingMethod("#3") } + sequencer.post { suspendingMethod("#1") }.also { + results.add(it) + } }, GlobalScope.launch(dispatcher) { - sequencer.post("Sequencer1") { suspendingMethod("#4") } + sequencer.post { suspendingMethod("#2") }.also { + results.add(it) + } }, GlobalScope.launch(dispatcher) { - sequencer.post("Sequencer2") { suspendingMethod("#5") } - }, - GlobalScope.launch(dispatcher) { - sequencer.post("Sequencer2") { suspendingMethod("#6") } - }, - GlobalScope.launch(dispatcher) { - sequencer.post("Sequencer2") { suspendingMethod("#7") } + sequencer.post { suspendingMethod("#3") }.also { + results.add(it) + } } ) - Thread.sleep(5500) - sequencer.cancelAll() runBlocking { jobs.joinAll() } + assertEquals(3, results.size) + assertEquals(results[0], "#1") + assertEquals(results[1], "#2") + assertEquals(results[2], "#3") } - private suspend fun suspendingMethod(name: String): String = withContext(Dispatchers.Default) { - println("BLOCKING METHOD $name STARTS") - delay(3000) - println("BLOCKING METHOD $name ENDS") - name + @Test + fun sequencer_should_run_parallel() { + val sequencer1 = ChannelCoroutineSequencer() + val sequencer2 = ChannelCoroutineSequencer() + val sequencer3 = ChannelCoroutineSequencer() + val results = ArrayList() + val jobs = listOf( + GlobalScope.launch(dispatcher) { + sequencer1.post { suspendingMethod("#1") }.also { + results.add(it) + } + }, + GlobalScope.launch(dispatcher) { + sequencer2.post { suspendingMethod("#2") }.also { + results.add(it) + } + }, + GlobalScope.launch(dispatcher) { + sequencer3.post { suspendingMethod("#3") }.also { + results.add(it) + } + } + ) + runBlocking { + jobs.joinAll() + } + assertEquals(3, results.size) + } + + @Test + fun sequencer_should_jump_to_next_when_current_job_canceled() { + val sequencer = ChannelCoroutineSequencer() + val results = ArrayList() + val jobs = listOf( + GlobalScope.launch(dispatcher) { + sequencer.post { suspendingMethod("#1") }.also { + results.add(it) + } + + }, + GlobalScope.launch(dispatcher) { + val result = sequencer.post { suspendingMethod("#2") }.also { + results.add(it) + } + println("Result: $result") + }, + GlobalScope.launch(dispatcher) { + sequencer.post { suspendingMethod("#3") }.also { + results.add(it) + } + } + ) + // We are canceling the second job + jobs[1].cancel() + runBlocking { + jobs.joinAll() + } + assertEquals(2, results.size) + } + + private suspend fun suspendingMethod(name: String): String { + println("BLOCKING METHOD $name STARTS on ${Thread.currentThread().name}") + delay(1000) + println("BLOCKING METHOD $name ENDS on ${Thread.currentThread().name}") + return name } } From fe2be90002519986e29956380f69e4a4d2fcd19b Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Dec 2019 15:30:57 +0100 Subject: [PATCH 05/61] Sync: use the CoroutineSequencer but need more tests --- .../android/internal/di/MatrixModule.kt | 3 +- .../internal/session/DefaultSession.kt | 3 ++ .../android/internal/session/sync/SyncTask.kt | 9 +++-- .../session/sync/SyncTaskSequencer.kt | 24 +++++++++++++ .../internal/session/sync/job/SyncService.kt | 2 +- .../internal/session/sync/job/SyncThread.kt | 20 +++++------ .../internal/session/sync/job/SyncWorker.kt | 8 +---- .../internal/task/CoroutineSequencer.kt | 35 ++++++++++--------- .../android/internal/task/TaskExecutor.kt | 1 - .../android/internal/task/TaskThread.kt | 3 +- .../util/MatrixCoroutineDispatchers.kt | 3 +- ...cersTest.kt => CoroutineSequencersTest.kt} | 2 +- 12 files changed, 68 insertions(+), 45 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTaskSequencer.kt rename matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/{MatrixCoroutineSequencersTest.kt => CoroutineSequencersTest.kt} (99%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt index c17864b82b..8b37d01b70 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt @@ -38,8 +38,7 @@ internal object MatrixModule { return MatrixCoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default, main = Dispatchers.Main, - crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher(), - sync = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + crypto = createBackgroundHandler("Crypto_Thread").asCoroutineDispatcher() ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 32126fb78d..ac86d437ea 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -44,6 +44,7 @@ import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.database.LiveEntityObserver +import im.vector.matrix.android.internal.session.sync.SyncTaskSequencer import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncWorker @@ -74,6 +75,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se private val syncThreadProvider: Provider, private val contentUrlResolver: ContentUrlResolver, private val syncTokenStore: SyncTokenStore, + private val syncTaskSequencer: SyncTaskSequencer, private val contentUploadProgressTracker: ContentUploadStateTracker, private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy) @@ -143,6 +145,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se cryptoService.get().close() isOpen = false EventBus.getDefault().unregister(this) + syncTaskSequencer.close() } override fun syncState(): LiveData { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 1082fa835b..688f159fb0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -45,10 +45,15 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, private val initialSyncProgressService: DefaultInitialSyncProgressService, private val syncTokenStore: SyncTokenStore, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, - private val userStore: UserStore + private val userStore: UserStore, + private val syncTaskSequencer: SyncTaskSequencer ) : SyncTask { - override suspend fun execute(params: SyncTask.Params) { + override suspend fun execute(params: SyncTask.Params) = syncTaskSequencer.post { + doSync(params) + } + + private suspend fun doSync(params: SyncTask.Params) { Timber.v("Sync task started on Thread: ${Thread.currentThread().name}") // Maybe refresh the home server capabilities data we know getHomeServerCapabilitiesTask.execute(Unit) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTaskSequencer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTaskSequencer.kt new file mode 100644 index 0000000000..bfa49b7af5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTaskSequencer.kt @@ -0,0 +1,24 @@ +/* + * 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.matrix.android.internal.session.sync + +import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.task.ChannelCoroutineSequencer +import javax.inject.Inject + +@SessionScope +internal class SyncTaskSequencer @Inject constructor() : ChannelCoroutineSequencer() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 8a91186e07..ce46531045 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -63,7 +63,7 @@ abstract class SyncService : Service() { Timber.i("Received a start while was already syncing... ignore") } else { isRunning.set(true) - serviceScope.launch(coroutineDispatchers.sync) { + serviceScope.launch(coroutineDispatchers.io) { doSync() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 01923b103f..1acb7dbb4a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -30,8 +30,7 @@ import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.BackgroundDetectionObserver -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.* import timber.log.Timber import java.net.SocketTimeoutException import java.util.concurrent.CountDownLatch @@ -42,14 +41,13 @@ private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L internal class SyncThread @Inject constructor(private val syncTask: SyncTask, private val networkConnectivityChecker: NetworkConnectivityChecker, - private val backgroundDetectionObserver: BackgroundDetectionObserver, - private val taskExecutor: TaskExecutor -) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { + private val backgroundDetectionObserver: BackgroundDetectionObserver) + : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private var state: SyncState = SyncState.IDLE private var liveState = MutableLiveData() private val lock = Object() - private var cancelableTask: Cancelable? = null + private val syncScope = CoroutineScope(SupervisorJob()) private var isStarted = false @@ -74,14 +72,14 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, if (isStarted) { Timber.v("Pause sync...") isStarted = false - cancelableTask?.cancel() + syncScope.coroutineContext.cancelChildren() } } fun kill() = synchronized(lock) { Timber.v("Kill sync...") updateStateTo(SyncState.KILLING) - cancelableTask?.cancel() + syncScope.coroutineContext.cancelChildren() lock.notify() } @@ -101,7 +99,6 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, isStarted = true networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) - while (state != SyncState.KILLING) { Timber.v("Entering loop, state: $state") if (!networkConnectivityChecker.hasInternetAccess()) { @@ -122,9 +119,12 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, val timeout = state.let { if (it is SyncState.RUNNING && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT } Timber.v("Execute sync request with timeout $timeout") val params = SyncTask.Params(timeout) - runBlocking { + val sync = syncScope.launch { doSync(params) } + runBlocking { + sync.join() + } Timber.v("...Continue") } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index 804102913d..eb4f2ff7c2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -18,19 +18,14 @@ package im.vector.matrix.android.internal.session.sync.job import android.content.Context import androidx.work.* import com.squareup.moshi.JsonClass -import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.isTokenError import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent -import kotlinx.coroutines.delay -import kotlinx.coroutines.withContext import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -50,7 +45,6 @@ internal class SyncWorker(context: Context, @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor - @Inject lateinit var coroutineDispatchers: MatrixCoroutineDispatchers @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker override suspend fun doWork(): Result { @@ -72,7 +66,7 @@ internal class SyncWorker(context: Context, ) } - private suspend fun doSync(timeout: Long) = withContext(coroutineDispatchers.sync) { + private suspend fun doSync(timeout: Long) { val taskParams = SyncTask.Params(timeout) syncTask.execute(taskParams) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt index 0f8439b144..1df0dae901 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt @@ -16,32 +16,36 @@ package im.vector.matrix.android.internal.task -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.intrinsics.startCoroutineCancellable -import kotlinx.coroutines.launch import java.util.concurrent.Executors +/** + * This class intends to be used for ensure suspendable methods are played sequentially all the way long. + */ internal interface CoroutineSequencer { + /** + * @param block the suspendable block to execute + * @return the result of the block + */ suspend fun post(block: suspend () -> T): T - fun cancel() + + /** + * Cancel all and close, so you won't be able to post anything else after + */ fun close() } -internal class ChannelCoroutineSequencer : CoroutineSequencer { +internal open class ChannelCoroutineSequencer : CoroutineSequencer { private data class Message( val block: suspend () -> T, val deferred: CompletableDeferred ) - private val messageChannel: Channel> = Channel() + private var messageChannel: Channel> = Channel() private val coroutineScope = CoroutineScope(SupervisorJob()) + // This will ensure private val singleDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() init { @@ -62,13 +66,8 @@ internal class ChannelCoroutineSequencer : CoroutineSequencer { } override fun close() { - messageChannel.cancel() coroutineScope.coroutineContext.cancelChildren() - } - - override fun cancel() { - close() - launchCoroutine() + messageChannel.close() } override suspend fun post(block: suspend () -> T): T { @@ -78,6 +77,8 @@ internal class ChannelCoroutineSequencer : CoroutineSequencer { return try { deferred.await() } catch (cancellation: CancellationException) { + // In case of cancellation, we stop the current coroutine context + // and relaunch one to consume next messages coroutineScope.coroutineContext.cancelChildren() launchCoroutine() throw cancellation diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt index d5392779d1..244cc83901 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt @@ -85,6 +85,5 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers TaskThread.IO -> coroutineDispatchers.io TaskThread.CALLER -> EmptyCoroutineContext TaskThread.CRYPTO -> coroutineDispatchers.crypto - TaskThread.SYNC -> coroutineDispatchers.sync } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt index 16ed93662c..c04e9fbce6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskThread.kt @@ -21,6 +21,5 @@ internal enum class TaskThread { COMPUTATION, IO, CALLER, - CRYPTO, - SYNC + CRYPTO } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt index 23201c084e..d15389f703 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/MatrixCoroutineDispatchers.kt @@ -22,6 +22,5 @@ internal data class MatrixCoroutineDispatchers( val io: CoroutineDispatcher, val computation: CoroutineDispatcher, val main: CoroutineDispatcher, - val crypto: CoroutineDispatcher, - val sync: CoroutineDispatcher + val crypto: CoroutineDispatcher ) diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt similarity index 99% rename from matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt rename to matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt index c69146c3dc..a367632d9f 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/MatrixCoroutineSequencersTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt @@ -26,7 +26,7 @@ import org.junit.Assert.assertEquals import org.junit.Test import java.util.concurrent.Executors -class MatrixCoroutineSequencersTest { +class CoroutineSequencersTest { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() From 5dd46e82d75b5816df11c93883cfbb78acd40a5f Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 13 Dec 2019 18:21:44 +0100 Subject: [PATCH 06/61] Sync: make only one big transaction to avoid having bad states --- .../android/internal/di/MatrixModule.kt | 1 - .../internal/session/sync/GroupSyncHandler.kt | 18 +- .../internal/session/sync/RoomSyncHandler.kt | 48 ++--- .../session/sync/SyncResponseHandler.kt | 72 +++++-- .../android/internal/session/sync/SyncTask.kt | 2 - .../internal/session/sync/SyncTokenStore.kt | 9 +- .../sync/UserAccountDataSyncHandler.kt | 193 +++++++++++------- .../internal/session/sync/job/SyncThread.kt | 6 - .../internal/task/CoroutineSequencer.kt | 2 - .../internal/task/CoroutineSequencersTest.kt | 2 - .../vector/riotx/core/extensions/Session.kt | 2 - .../im/vector/riotx/features/MainActivity.kt | 1 - 12 files changed, 196 insertions(+), 160 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt index 8b37d01b70..04b8565546 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher import org.matrix.olm.OlmManager -import java.util.concurrent.Executors @Module internal object MatrixModule { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt index 2ca9b6cccc..392db0a73c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/GroupSyncHandler.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.session.sync -import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.model.GroupEntity @@ -25,11 +24,10 @@ import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressServi import im.vector.matrix.android.internal.session.mapWithProgress import im.vector.matrix.android.internal.session.sync.model.GroupsSyncResponse import im.vector.matrix.android.internal.session.sync.model.InvitedGroupSync -import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm import javax.inject.Inject -internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarchy) { +internal class GroupSyncHandler @Inject constructor() { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -37,12 +35,14 @@ internal class GroupSyncHandler @Inject constructor(private val monarchy: Monarc data class LEFT(val data: Map) : HandlingStrategy() } - suspend fun handle(roomsSyncResponse: GroupsSyncResponse, reporter: DefaultInitialSyncProgressService? = null) { - monarchy.awaitTransaction { realm -> - handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter) - handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter) - handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter) - } + fun handle( + realm: Realm, + roomsSyncResponse: GroupsSyncResponse, + reporter: DefaultInitialSyncProgressService? = null + ) { + handleGroupSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), reporter) + handleGroupSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), reporter) + handleGroupSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), reporter) } // PRIVATE METHODS ***************************************************************************** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index 4a003eb7d9..a080a5158e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -16,9 +16,7 @@ package im.vector.matrix.android.internal.session.sync -import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R -import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel @@ -34,31 +32,21 @@ import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoo import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.mapWithProgress -import im.vector.matrix.android.internal.session.notification.DefaultPushRuleService -import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.read.FullyReadContent import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.model.* import im.vector.matrix.android.internal.session.user.UserEntityFactory -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm import io.realm.kotlin.createObject import timber.log.Timber import javax.inject.Inject -internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarchy, - private val readReceiptHandler: ReadReceiptHandler, +internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler, private val roomSummaryUpdater: RoomSummaryUpdater, private val roomTagHandler: RoomTagHandler, private val roomFullyReadHandler: RoomFullyReadHandler, - private val cryptoService: DefaultCryptoService, - private val tokenStore: SyncTokenStore, - private val pushRuleService: DefaultPushRuleService, - private val processForPushTask: ProcessEventForPushTask, - private val taskExecutor: TaskExecutor) { + private val cryptoService: DefaultCryptoService) { sealed class HandlingStrategy { data class JOINED(val data: Map) : HandlingStrategy() @@ -66,28 +54,16 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch data class LEFT(val data: Map) : HandlingStrategy() } - suspend fun handle(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, reporter: DefaultInitialSyncProgressService? = null) { + fun handle( + realm: Realm, + roomsSyncResponse: RoomsSyncResponse, + isInitialSync: Boolean, + reporter: DefaultInitialSyncProgressService? = null + ) { Timber.v("Execute transaction from $this") - monarchy.awaitTransaction { realm -> - handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter) - handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter) - handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) - } - // handle event for bing rule checks - checkPushRules(roomsSyncResponse) - } - - private fun checkPushRules(roomsSyncResponse: RoomsSyncResponse) { - Timber.v("[PushRules] --> checkPushRules") - if (tokenStore.getLastToken() == null) { - Timber.v("[PushRules] <-- No push rule check on initial sync") - return - } // nothing on initial sync - - val rules = pushRuleService.getPushRules(RuleScope.GLOBAL) - processForPushTask.configureWith(ProcessEventForPushTask.Params(roomsSyncResponse, rules)) - .executeBy(taskExecutor) - Timber.v("[PushRules] <-- Push task scheduled") + handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter) + handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter) + handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) } // PRIVATE METHODS ***************************************************************************** @@ -137,7 +113,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() - ?: Int.MIN_VALUE + ?: Int.MIN_VALUE val untimelinedStateIndex = minStateIndex + 1 roomSync.state.events.forEach { event -> roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt index 1ae185b073..1454fdae7d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncResponseHandler.kt @@ -16,20 +16,30 @@ package im.vector.matrix.android.internal.session.sync +import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.R +import im.vector.matrix.android.api.pushrules.PushRuleService +import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService +import im.vector.matrix.android.internal.session.notification.ProcessEventForPushTask import im.vector.matrix.android.internal.session.reportSubtask +import im.vector.matrix.android.internal.session.sync.model.RoomsSyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse +import im.vector.matrix.android.internal.util.awaitTransaction import timber.log.Timber import javax.inject.Inject import kotlin.system.measureTimeMillis -internal class SyncResponseHandler @Inject constructor(private val roomSyncHandler: RoomSyncHandler, +internal class SyncResponseHandler @Inject constructor(private val monarchy: Monarchy, + private val roomSyncHandler: RoomSyncHandler, private val userAccountDataSyncHandler: UserAccountDataSyncHandler, private val groupSyncHandler: GroupSyncHandler, private val cryptoSyncHandler: CryptoSyncHandler, private val cryptoService: DefaultCryptoService, + private val tokenStore: SyncTokenStore, + private val processEventForPushTask: ProcessEventForPushTask, + private val pushRuleService: PushRuleService, private val initialSyncProgressService: DefaultInitialSyncProgressService) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?) { @@ -45,26 +55,27 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl }.also { Timber.v("Finish handling start cryptoService in $it ms") } - val measure = measureTimeMillis { - // Handle the to device events before the room ones - // to ensure to decrypt them properly - measureTimeMillis { - Timber.v("Handle toDevice") - reportSubtask(reporter, R.string.initial_sync_start_importing_account_crypto, 100, 0.1f) { - if (syncResponse.toDevice != null) { - cryptoSyncHandler.handleToDevice(syncResponse.toDevice, reporter) - } - } - }.also { - Timber.v("Finish handling toDevice in $it ms") - } + // Handle the to device events before the room ones + // to ensure to decrypt them properly + measureTimeMillis { + Timber.v("Handle toDevice") + reportSubtask(reporter, R.string.initial_sync_start_importing_account_crypto, 100, 0.1f) { + if (syncResponse.toDevice != null) { + cryptoSyncHandler.handleToDevice(syncResponse.toDevice, reporter) + } + } + }.also { + Timber.v("Finish handling toDevice in $it ms") + } + + // Start one big transaction + monarchy.awaitTransaction { realm -> measureTimeMillis { Timber.v("Handle rooms") - reportSubtask(reporter, R.string.initial_sync_start_importing_account_rooms, 100, 0.7f) { if (syncResponse.rooms != null) { - roomSyncHandler.handle(syncResponse.rooms, isInitialSync, reporter) + roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, reporter) } } }.also { @@ -75,7 +86,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl reportSubtask(reporter, R.string.initial_sync_start_importing_account_groups, 100, 0.1f) { Timber.v("Handle groups") if (syncResponse.groups != null) { - groupSyncHandler.handle(syncResponse.groups, reporter) + groupSyncHandler.handle(realm, syncResponse.groups, reporter) } } }.also { @@ -85,15 +96,32 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl measureTimeMillis { reportSubtask(reporter, R.string.initial_sync_start_importing_account_data, 100, 0.1f) { Timber.v("Handle accountData") - userAccountDataSyncHandler.handle(syncResponse.accountData, syncResponse.rooms?.invite) + userAccountDataSyncHandler.handle(realm, syncResponse.accountData) } }.also { Timber.v("Finish handling accountData in $it ms") } - - Timber.v("On sync completed") - cryptoSyncHandler.onSyncCompleted(syncResponse) + tokenStore.saveToken(realm, syncResponse.nextBatch) } - Timber.v("Finish handling sync in $measure ms") + + // Everything else we need to do outside the transaction + syncResponse.rooms?.also { + checkPushRules(it, isInitialSync) + userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) + } + Timber.v("On sync completed") + cryptoSyncHandler.onSyncCompleted(syncResponse) + } + + private suspend fun checkPushRules(roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean) { + Timber.v("[PushRules] --> checkPushRules") + if (isInitialSync) { + Timber.v("[PushRules] <-- No push rule check on initial sync") + return + } // nothing on initial sync + + val rules = pushRuleService.getPushRules(RuleScope.GLOBAL) + processEventForPushTask.execute(ProcessEventForPushTask.Params(roomsSyncResponse, rules)) + Timber.v("[PushRules] <-- Push task scheduled") } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 688f159fb0..fbc419d3b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -21,7 +21,6 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.di.UserId -import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.filter.FilterRepository @@ -88,7 +87,6 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, throw throwable } syncResponseHandler.handleResponse(syncResponse, token) - syncTokenStore.saveToken(syncResponse.nextBatch) if (isInitialSync) { initialSyncProgressService.endAll() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTokenStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTokenStore.kt index 350f2a1d83..d0af7d3ff5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTokenStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTokenStore.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.sync import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.model.SyncEntity -import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm import javax.inject.Inject @@ -30,10 +29,8 @@ internal class SyncTokenStore @Inject constructor(private val monarchy: Monarchy } } - suspend fun saveToken(token: String?) { - monarchy.awaitTransaction { - val sync = SyncEntity(token) - it.insertOrUpdate(sync) - } + fun saveToken(realm: Realm, token: String?) { + val sync = SyncEntity(token) + realm.insertOrUpdate(sync) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 9cc3a5a3c6..3deb612756 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -17,44 +17,39 @@ package im.vector.matrix.android.internal.session.sync import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.pushrules.RuleScope +import im.vector.matrix.android.api.pushrules.RuleSetKey import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.getDirectRooms +import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.UserId -import im.vector.matrix.android.internal.session.pushers.SavePushRulesTask import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync import im.vector.matrix.android.internal.session.sync.model.accountdata.* import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper -import im.vector.matrix.android.internal.session.user.accountdata.SaveBreadcrumbsTask -import im.vector.matrix.android.internal.session.user.accountdata.SaveIgnoredUsersTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.awaitTransaction import io.realm.Realm +import io.realm.RealmList import timber.log.Timber import javax.inject.Inject internal class UserAccountDataSyncHandler @Inject constructor(private val monarchy: Monarchy, @UserId private val userId: String, private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val savePushRulesTask: SavePushRulesTask, - private val saveIgnoredUsersTask: SaveIgnoredUsersTask, - private val saveBreadcrumbsTask: SaveBreadcrumbsTask, - private val taskExecutor: TaskExecutor) { + private val updateUserAccountDataTask: UpdateUserAccountDataTask) { - suspend fun handle(accountData: UserAccountDataSync?, invites: Map?) { + fun handle(realm: Realm, accountData: UserAccountDataSync?) { accountData?.list?.forEach { when (it) { - is UserAccountDataDirectMessages -> handleDirectChatRooms(it) - is UserAccountDataPushRules -> handlePushRules(it) - is UserAccountDataIgnoredUsers -> handleIgnoredUsers(it) - is UserAccountDataBreadcrumbs -> handleBreadcrumbs(it) + is UserAccountDataDirectMessages -> handleDirectChatRooms(realm, it) + is UserAccountDataPushRules -> handlePushRules(realm, it) + is UserAccountDataIgnoredUsers -> handleIgnoredUsers(realm, it) + is UserAccountDataBreadcrumbs -> handleBreadcrumbs(realm, it) is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}") else -> error("Missing code here!") } @@ -65,78 +60,134 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc // it.toString() // MoshiProvider.providesMoshi() // } - - monarchy.doWithRealm { realm -> - synchronizeWithServerIfNeeded(realm, invites) - } - } - - private suspend fun handlePushRules(userAccountDataPushRules: UserAccountDataPushRules) { - savePushRulesTask.execute(SavePushRulesTask.Params(userAccountDataPushRules.content)) - } - - private suspend fun handleDirectChatRooms(directMessages: UserAccountDataDirectMessages) { - monarchy.awaitTransaction { realm -> - val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm) - oldDirectRooms.forEach { - it.isDirect = false - it.directUserId = null - } - directMessages.content.forEach { - val userId = it.key - it.value.forEach { roomId -> - val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - if (roomSummaryEntity != null) { - roomSummaryEntity.isDirect = true - roomSummaryEntity.directUserId = userId - realm.insertOrUpdate(roomSummaryEntity) - } - } - } - } } // If we get some direct chat invites, we synchronize the user account data including those. - private fun synchronizeWithServerIfNeeded(realm: Realm, invites: Map?) { + suspend fun synchronizeWithServerIfNeeded(invites: Map) { if (invites.isNullOrEmpty()) return val directChats = directChatsHelper.getLocalUserAccount() var hasUpdate = false - invites.forEach { (roomId, _) -> - val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(userId) - val inviterId = myUserStateEvent?.sender - val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() } - val isDirect = myUserRoomMember?.isDirect - if (inviterId != null && inviterId != userId && isDirect == true) { - directChats - .getOrPut(inviterId, { arrayListOf() }) - .apply { - if (contains(roomId)) { - Timber.v("Direct chats already include room $roomId with user $inviterId") - } else { - add(roomId) - hasUpdate = true + monarchy.doWithRealm { realm -> + invites.forEach { (roomId, _) -> + val myUserStateEvent = RoomMembers(realm, roomId).getStateEvent(userId) + val inviterId = myUserStateEvent?.sender + val myUserRoomMember: RoomMember? = myUserStateEvent?.let { it.asDomain().content?.toModel() } + val isDirect = myUserRoomMember?.isDirect + if (inviterId != null && inviterId != userId && isDirect == true) { + directChats + .getOrPut(inviterId, { arrayListOf() }) + .apply { + if (contains(roomId)) { + Timber.v("Direct chats already include room $roomId with user $inviterId") + } else { + add(roomId) + hasUpdate = true + } } - } + } } } if (hasUpdate) { val updateUserAccountParams = UpdateUserAccountDataTask.DirectChatParams( directMessages = directChats ) - updateUserAccountDataTask.configureWith(updateUserAccountParams).executeBy(taskExecutor) + updateUserAccountDataTask.execute(updateUserAccountParams) } } - private fun handleIgnoredUsers(userAccountDataIgnoredUsers: UserAccountDataIgnoredUsers) { - saveIgnoredUsersTask - .configureWith(SaveIgnoredUsersTask.Params(userAccountDataIgnoredUsers.content.ignoredUsers.keys.toList())) - .executeBy(taskExecutor) + + private fun handlePushRules(realm: Realm, userAccountDataPushRules: UserAccountDataPushRules) { + val pushRules = userAccountDataPushRules.content + realm.where(PushRulesEntity::class.java) + .findAll() + .deleteAllFromRealm() + + // Save only global rules for the moment + val globalRules = pushRules.global + + val content = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.CONTENT } + globalRules.content?.forEach { rule -> + content.pushRules.add(PushRulesMapper.map(rule)) + } + realm.insertOrUpdate(content) + + val override = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.OVERRIDE } + globalRules.override?.forEach { rule -> + PushRulesMapper.map(rule).also { + override.pushRules.add(it) + } + } + realm.insertOrUpdate(override) + + val rooms = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.ROOM } + globalRules.room?.forEach { rule -> + rooms.pushRules.add(PushRulesMapper.map(rule)) + } + realm.insertOrUpdate(rooms) + + val senders = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.SENDER } + globalRules.sender?.forEach { rule -> + senders.pushRules.add(PushRulesMapper.map(rule)) + } + realm.insertOrUpdate(senders) + + val underrides = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.UNDERRIDE } + globalRules.underride?.forEach { rule -> + underrides.pushRules.add(PushRulesMapper.map(rule)) + } + realm.insertOrUpdate(underrides) + } + + private fun handleDirectChatRooms(realm: Realm, directMessages: UserAccountDataDirectMessages) { + val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm) + oldDirectRooms.forEach { + it.isDirect = false + it.directUserId = null + } + directMessages.content.forEach { + val userId = it.key + it.value.forEach { roomId -> + val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() + if (roomSummaryEntity != null) { + roomSummaryEntity.isDirect = true + roomSummaryEntity.directUserId = userId + realm.insertOrUpdate(roomSummaryEntity) + } + } + } + } + + private fun handleIgnoredUsers(realm: Realm, userAccountDataIgnoredUsers: UserAccountDataIgnoredUsers) { + val userIds = userAccountDataIgnoredUsers.content.ignoredUsers.keys + realm.where(IgnoredUserEntity::class.java) + .findAll() + .deleteAllFromRealm() + // And save the new received list + userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } } // TODO If not initial sync, we should execute a init sync } - private fun handleBreadcrumbs(userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) { - saveBreadcrumbsTask - .configureWith(SaveBreadcrumbsTask.Params(userAccountDataBreadcrumbs.content.recentRoomIds)) - .executeBy(taskExecutor) + private fun handleBreadcrumbs(realm: Realm, userAccountDataBreadcrumbs: UserAccountDataBreadcrumbs) { + val recentRoomIds = userAccountDataBreadcrumbs.content.recentRoomIds + val entity = BreadcrumbsEntity.getOrCreate(realm) + + // And save the new received list + entity.recentRoomIds = RealmList().apply { addAll(recentRoomIds) } + + // Update the room summaries + // Reset all the indexes... + RoomSummaryEntity.where(realm) + .greaterThan(RoomSummaryEntityFields.BREADCRUMBS_INDEX, RoomSummaryEntity.NOT_IN_BREADCRUMBS) + .findAll() + .forEach { + it.breadcrumbsIndex = RoomSummaryEntity.NOT_IN_BREADCRUMBS + } + + // ...and apply new indexes + recentRoomIds.forEachIndexed { index, roomId -> + RoomSummaryEntity.where(realm, roomId) + .findFirst() + ?.breadcrumbsIndex = index + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 1acb7dbb4a..cf7d3ce63d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -19,21 +19,15 @@ package im.vector.matrix.android.internal.session.sync.job import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.squareup.moshi.JsonEncodingException -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.session.sync.SyncState -import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.TaskThread -import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import kotlinx.coroutines.* import timber.log.Timber import java.net.SocketTimeoutException -import java.util.concurrent.CountDownLatch import javax.inject.Inject private const val RETRY_WAIT_TIME_MS = 10_000L diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt index 1df0dae901..7062c63816 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/CoroutineSequencer.kt @@ -84,6 +84,4 @@ internal open class ChannelCoroutineSequencer : CoroutineSequencer { throw cancellation } } - } - diff --git a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt index a367632d9f..9591feaa32 100644 --- a/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt +++ b/matrix-sdk-android/src/test/java/im/vector/matrix/android/internal/task/CoroutineSequencersTest.kt @@ -99,7 +99,6 @@ class CoroutineSequencersTest { sequencer.post { suspendingMethod("#1") }.also { results.add(it) } - }, GlobalScope.launch(dispatcher) { val result = sequencer.post { suspendingMethod("#2") }.also { @@ -127,5 +126,4 @@ class CoroutineSequencersTest { println("BLOCKING METHOD $name ENDS on ${Thread.currentThread().name}") return name } - } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index 9a6905e4c7..3c201542e4 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -17,13 +17,11 @@ package im.vector.riotx.core.extensions import android.content.Context -import android.content.Intent import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.ProcessLifecycleOwner import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.sync.FilterService -import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.riotx.core.services.VectorSyncService import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.session.SessionListener diff --git a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt index b95eddd011..20a71803d8 100644 --- a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt @@ -26,7 +26,6 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter -import im.vector.riotx.core.extensions.configureAndStart import im.vector.riotx.core.extensions.startSyncing import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.utils.deleteAllFiles From a4aa38ee43e69347e6b8e1c4ff74d5c65b9d8e58 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Dec 2019 17:14:26 +0100 Subject: [PATCH 07/61] Fix new issue on permalink click --- CHANGES.md | 2 +- .../im/vector/riotx/features/permalink/PermalinkHandler.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1228ea65ca..5f83727cd7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,7 +14,7 @@ Other changes: Bugfix 🐛: - Scroll breadcrumbs to top when opened - Render default room name when it starts with an emoji (#477) - - Do not display " (IRC)") in display names https://github.com/vector-im/riot-android/issues/444 + - Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444 Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt index c849166738..e46adc53fc 100644 --- a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt @@ -57,7 +57,7 @@ class PermalinkHandler @Inject constructor(private val session: Session, .observeOn(AndroidSchedulers.mainThread()) .map { val roomId = it.getOrNull() - if (navigateToRoomInterceptor?.navToRoom(roomId) != true) { + if (navigateToRoomInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) { openRoom(context, roomId, permalinkData.eventId, buildTask) } true @@ -87,9 +87,9 @@ class PermalinkHandler @Inject constructor(private val session: Session, } /** - * Open room either joined, or not unknown + * Open room either joined, or not */ - private fun openRoom(context: Context, roomId: String?, eventId: String? = null, buildTask: Boolean) { + private fun openRoom(context: Context, roomId: String?, eventId: String?, buildTask: Boolean) { return if (roomId != null && session.getRoom(roomId) != null) { navigator.openRoom(context, roomId, eventId, buildTask) } else { From 1c727c1ee47191b81486bf0356e383a26da03c0b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2019 10:42:58 +0100 Subject: [PATCH 08/61] Fix crash reported by rageshake --- .../features/home/room/detail/RoomDetailFragment.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 8887b94f92..49f23f7f2c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -86,8 +86,6 @@ import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotx.features.command.Command import im.vector.riotx.features.home.AvatarRenderer -import im.vector.riotx.features.permalink.NavigateToRoomInterceptor -import im.vector.riotx.features.permalink.PermalinkHandler import im.vector.riotx.features.home.getColorFromUserId import im.vector.riotx.features.home.room.detail.composer.TextComposerAction import im.vector.riotx.features.home.room.detail.composer.TextComposerView @@ -109,6 +107,8 @@ import im.vector.riotx.features.media.ImageMediaViewerActivity import im.vector.riotx.features.media.VideoContentRenderer import im.vector.riotx.features.media.VideoMediaViewerActivity import im.vector.riotx.features.notifications.NotificationDrawerManager +import im.vector.riotx.features.permalink.NavigateToRoomInterceptor +import im.vector.riotx.features.permalink.PermalinkHandler import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.share.SharedData @@ -416,8 +416,10 @@ class RoomDetailFragment @Inject constructor( composerLayout.composerRelatedMessageAvatar ) composerLayout.expand { - // need to do it here also when not using quick reply - focusComposerAndShowKeyboard() + if (isAdded) { + // need to do it here also when not using quick reply + focusComposerAndShowKeyboard() + } } focusComposerAndShowKeyboard() } From 42cdb1db1146a65d8a270eb23e5aac29e73edda8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2019 12:26:45 +0100 Subject: [PATCH 09/61] Fix crash reported by rageshake: writeToFile may throw exceptions --- .../android/internal/session/DefaultFileService.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt index 868d63665a..c160ac9b31 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt @@ -83,15 +83,13 @@ internal class DefaultFileService @Inject constructor(private val context: Conte if (elementToDecrypt != null) { Timber.v("## decrypt file") - MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) ?: throw IllegalStateException("Decryption error") - } else { - inputStream + MXEncryptedAttachments.decryptAttachment(inputStream, elementToDecrypt) + ?: throw IllegalStateException("Decryption error") } + + writeToFile(inputStream, destFile) + destFile } - .map { inputStream -> - writeToFile(inputStream, destFile) - destFile - } } else { Try.just(destFile) } From 1ceddd9607674bf5faa6278b04327b2618d53071 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2019 14:05:58 +0100 Subject: [PATCH 10/61] Rageshake: log resumed screens and add the log verbosity ON/OFF to the rageshakes data --- .../vector/riotx/core/platform/VectorBaseActivity.kt | 3 +-- .../platform/VectorBaseBottomSheetDialogFragment.kt | 6 ++++++ .../vector/riotx/core/platform/VectorBaseFragment.kt | 2 +- .../im/vector/riotx/features/rageshake/BugReporter.kt | 11 ++++++++--- .../riotx/features/rageshake/VectorFileLogger.kt | 7 +++---- .../features/settings/VectorSettingsBaseFragment.kt | 2 +- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index 02e28e079c..f70aed9393 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -222,8 +222,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { override fun onResume() { super.onResume() - - Timber.v("onResume Activity ${this.javaClass.simpleName}") + Timber.i("onResume Activity ${this.javaClass.simpleName}") configurationViewModel.onActivityResumed() diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt index 70311e2f57..b3a56f48ee 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseBottomSheetDialogFragment.kt @@ -32,6 +32,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.utils.DimensionConverter +import timber.log.Timber /** * Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment) @@ -80,6 +81,11 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment() super.onCreate(savedInstanceState) } + override fun onResume() { + super.onResume() + Timber.i("onResume BottomSheet ${this.javaClass.simpleName}") + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return super.onCreateDialog(savedInstanceState).apply { val dialog = this as? BottomSheetDialog diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index efcbdfff39..8e1ad72150 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -104,7 +104,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { @CallSuper override fun onResume() { super.onResume() - Timber.v("onResume Fragment ${this.javaClass.simpleName}") + Timber.i("onResume Fragment ${this.javaClass.simpleName}") } @CallSuper diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt index b96542a8ce..dc353363d5 100755 --- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt @@ -33,6 +33,7 @@ import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.extensions.toOnOff import im.vector.riotx.core.utils.getDeviceLocale import im.vector.riotx.features.settings.VectorLocale +import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.version.VersionProvider import okhttp3.Call @@ -44,12 +45,15 @@ import okhttp3.Response import org.json.JSONException import org.json.JSONObject import timber.log.Timber -import java.io.* +import java.io.File +import java.io.IOException +import java.io.OutputStreamWriter import java.net.HttpURLConnection -import java.util.Locale +import java.util.* import java.util.zip.GZIPOutputStream import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList /** * BugReporter creates and sends the bug reports. @@ -57,6 +61,7 @@ import javax.inject.Singleton @Singleton class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val versionProvider: VersionProvider, + private val vectorPreferences: VectorPreferences, private val vectorFileLogger: VectorFileLogger) { var inMultiWindowMode = false @@ -230,7 +235,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes .addFormDataPart("matrix_sdk_version", Matrix.getSdkVersion()) .addFormDataPart("olm_version", olmVersion) .addFormDataPart("device", Build.MODEL.trim()) - .addFormDataPart("lazy_loading", true.toOnOff()) + .addFormDataPart("verbose_log", vectorPreferences.labAllowedExtendedLogging().toOnOff()) .addFormDataPart("multi_window", inMultiWindowMode.toOnOff()) .addFormDataPart("os", Build.VERSION.RELEASE + " (API " + Build.VERSION.SDK_INT + ") " + Build.VERSION.INCREMENTAL + "-" + Build.VERSION.CODENAME) diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt index 95053790c8..6049db6180 100644 --- a/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt +++ b/vector/src/main/java/im/vector/riotx/features/rageshake/VectorFileLogger.kt @@ -24,9 +24,7 @@ import java.io.File import java.io.PrintWriter import java.io.StringWriter import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import java.util.TimeZone +import java.util.* import java.util.logging.* import java.util.logging.Formatter import javax.inject.Inject @@ -83,7 +81,8 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec return if (vectorPreferences.labAllowedExtendedLogging()) { false } else { - priority < Log.ERROR + // Exclude debug and verbose logs + priority <= Log.DEBUG } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsBaseFragment.kt index 666f1610b0..e32cc98123 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsBaseFragment.kt @@ -65,7 +65,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), HasScree override fun onResume() { super.onResume() - Timber.v("onResume Fragment ${this.javaClass.simpleName}") + Timber.i("onResume Fragment ${this.javaClass.simpleName}") vectorActivity.supportActionBar?.setTitle(titleRes) // find the view from parent activity mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views) From 65faedb06bf6c228fb36b8ece9eb933a8fca86bc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2019 14:14:49 +0100 Subject: [PATCH 11/61] BugReport screen: improve UX when description is too short (reported by Matthew) --- .../riotx/features/rageshake/BugReportActivity.kt | 13 ++++++++----- vector/src/main/res/layout/activity_bug_report.xml | 5 ++++- vector/src/main/res/values/strings_riotX.xml | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt index 187566f660..5c1428cb54 100755 --- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReportActivity.kt @@ -77,8 +77,7 @@ class BugReportActivity : VectorBaseActivity() { override fun onPrepareOptionsMenu(menu: Menu): Boolean { menu.findItem(R.id.ic_action_send_bug_report)?.let { - val isValid = bug_report_edit_text.text.toString().trim().length > 10 - && !bug_report_mask_view.isVisible + val isValid = !bug_report_mask_view.isVisible it.isEnabled = isValid it.icon.alpha = if (isValid) 255 else 100 @@ -90,7 +89,11 @@ class BugReportActivity : VectorBaseActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.ic_action_send_bug_report -> { - sendBugReport() + if (bug_report_edit_text.text.toString().trim().length >= 10) { + sendBugReport() + } else { + bug_report_text_input_layout.error = getString(R.string.bug_report_error_too_short) + } return true } } @@ -150,7 +153,7 @@ class BugReportActivity : VectorBaseActivity() { val myProgress = progress.coerceIn(0, 100) bug_report_progress_view.progress = myProgress - bug_report_progress_text_view.text = getString(R.string.send_bug_report_progress, "$myProgress") + bug_report_progress_text_view.text = getString(R.string.send_bug_report_progress, myProgress.toString()) } override fun onUploadSucceed() { @@ -179,7 +182,7 @@ class BugReportActivity : VectorBaseActivity() { @OnTextChanged(R.id.bug_report_edit_text) internal fun textChanged() { - invalidateOptionsMenu() + bug_report_text_input_layout.error = null } @OnCheckedChanged(R.id.bug_report_button_include_screenshot) diff --git a/vector/src/main/res/layout/activity_bug_report.xml b/vector/src/main/res/layout/activity_bug_report.xml index bbe819769a..7156f91314 100644 --- a/vector/src/main/res/layout/activity_bug_report.xml +++ b/vector/src/main/res/layout/activity_bug_report.xml @@ -1,5 +1,6 @@ + android:textColorHint="?attr/vctr_default_text_hint_color" + app:counterEnabled="true" + app:counterMaxLength="500"> The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by RiotX.\nPlease first clear data, then sign in again on another account. Your matrix.to link was malformed + The description is too short From 7fa76b9d351e99ae40179211fdfb2ad906ecb0b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2019 16:35:04 +0100 Subject: [PATCH 12/61] Prevent crash when opening unknown room, which should not happen... --- .../session/room/create/CreateRoomTask.kt | 2 +- .../java/im/vector/riotx/core/error/fatal.kt | 31 +++++++++++++++++++ .../features/navigation/DefaultNavigator.kt | 13 ++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/error/fatal.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index ca86765cd9..9af8434b7c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -52,7 +52,7 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro apiCall = roomAPI.createRoom(params) } val roomId = createRoomResponse.roomId!! - // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) + // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) val rql = RealmQueryLatch(realmConfiguration) { realm -> realm.where(RoomEntity::class.java) .equalTo(RoomEntityFields.ROOM_ID, roomId) diff --git a/vector/src/main/java/im/vector/riotx/core/error/fatal.kt b/vector/src/main/java/im/vector/riotx/core/error/fatal.kt new file mode 100644 index 0000000000..800e1ea7ad --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/error/fatal.kt @@ -0,0 +1,31 @@ +/* + * 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.riotx.core.error + +import im.vector.riotx.BuildConfig +import timber.log.Timber + +/** + * throw in debug, only log in production. As this method does not always throw, next statement should be a return + */ +fun fatalError(message: String) { + if (BuildConfig.DEBUG) { + error(message) + } else { + Timber.e(message) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 19f6aece58..08ff11217d 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -19,8 +19,11 @@ package im.vector.riotx.features.navigation import android.app.Activity import android.content.Context import android.content.Intent +import androidx.core.app.TaskStackBuilder import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.riotx.R +import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.error.fatalError import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.utils.toast import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity @@ -38,12 +41,18 @@ import im.vector.riotx.features.share.SharedData import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -import androidx.core.app.TaskStackBuilder @Singleton -class DefaultNavigator @Inject constructor() : Navigator { +class DefaultNavigator @Inject constructor( + private val sessionHolder: ActiveSessionHolder +) : Navigator { override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) { + if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) { + fatalError("Trying to open an unknown room $roomId") + return + } + val args = RoomDetailArgs(roomId, eventId) val intent = RoomDetailActivity.newIntent(context, args) if (buildTask) { From 79f11ad686d5baadf24ce556da555f31e205fccc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 17 Dec 2019 17:22:26 +0100 Subject: [PATCH 13/61] Prevent crash when mimetype is null --- .../api/session/content/ContentAttachmentData.kt | 2 +- .../api/session/room/model/message/VideoInfo.kt | 2 +- .../crypto/attachments/MXEncryptedAttachments.kt | 2 +- .../internal/session/content/FileUploader.kt | 4 ++-- .../session/room/send/LocalEchoEventFactory.kt | 4 ++-- .../features/attachments/AttachmentsMapper.kt | 15 ++++++++++----- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt index 933657b2fb..0d8ef2c52b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt @@ -30,7 +30,7 @@ data class ContentAttachmentData( val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val name: String? = null, val path: String, - val mimeType: String, + val mimeType: String?, val type: Type ) : Parcelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt index a7d4708d33..da247810cb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt @@ -25,7 +25,7 @@ data class VideoInfo( /** * The mimetype of the video e.g. "video/mp4". */ - @Json(name = "mimetype") val mimeType: String, + @Json(name = "mimetype") val mimeType: String?, /** * The width of the video in pixels. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt index 5f90b636ac..503bf8e4ff 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -49,7 +49,7 @@ object MXEncryptedAttachments { * @param mimetype the mime type * @return the encryption file info */ - fun encryptAttachment(attachmentStream: InputStream, mimetype: String): EncryptionResult { + fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult { val t0 = System.currentTimeMillis() val secureRandom = SecureRandom() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 209f03ad9d..2f4e991e62 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -43,9 +43,9 @@ internal class FileUploader @Inject constructor(@Authenticated suspend fun uploadFile(file: File, filename: String?, - mimeType: String, + mimeType: String?, progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { - val uploadBody = file.asRequestBody(mimeType.toMediaTypeOrNull()) + val uploadBody = file.asRequestBody(mimeType?.toMediaTypeOrNull()) return upload(uploadBody, filename, progressListener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 0fed1ca6f5..7a935783cf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -251,7 +251,7 @@ internal class LocalEchoEventFactory @Inject constructor( type = MessageType.MSGTYPE_AUDIO, body = attachment.name ?: "audio", audioInfo = AudioInfo( - mimeType = attachment.mimeType.takeIf { it.isNotBlank() } ?: "audio/mpeg", + mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "audio/mpeg", size = attachment.size ), url = attachment.path @@ -264,7 +264,7 @@ internal class LocalEchoEventFactory @Inject constructor( type = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", info = FileInfo( - mimeType = attachment.mimeType.takeIf { it.isNotBlank() } + mimeType = attachment.mimeType?.takeIf { it.isNotBlank() } ?: "application/octet-stream", size = attachment.size ), diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt index 5e843fcdfd..4b51c548a7 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt @@ -18,6 +18,7 @@ package im.vector.riotx.features.attachments import com.kbeanie.multipicker.api.entity.* import im.vector.matrix.android.api.session.content.ContentAttachmentData +import timber.log.Timber fun ChosenContact.toContactAttachment(): ContactAttachment { return ContactAttachment( @@ -29,6 +30,7 @@ fun ChosenContact.toContactAttachment(): ContactAttachment { } fun ChosenFile.toContentAttachmentData(): ContentAttachmentData { + if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( path = originalPath, mimeType = mimeType, @@ -40,6 +42,7 @@ fun ChosenFile.toContentAttachmentData(): ContentAttachmentData { } fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData { + if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( path = originalPath, mimeType = mimeType, @@ -51,16 +54,17 @@ fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData { ) } -fun ChosenFile.mapType(): ContentAttachmentData.Type { +private fun ChosenFile.mapType(): ContentAttachmentData.Type { return when { - mimeType.startsWith("image/") -> ContentAttachmentData.Type.IMAGE - mimeType.startsWith("video/") -> ContentAttachmentData.Type.VIDEO - mimeType.startsWith("audio/") -> ContentAttachmentData.Type.AUDIO - else -> ContentAttachmentData.Type.FILE + mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE + mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO + mimeType?.startsWith("audio/") == true -> ContentAttachmentData.Type.AUDIO + else -> ContentAttachmentData.Type.FILE } } fun ChosenImage.toContentAttachmentData(): ContentAttachmentData { + if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( path = originalPath, mimeType = mimeType, @@ -75,6 +79,7 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData { } fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData { + if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( path = originalPath, mimeType = mimeType, From 4c88c12cfe897c7076a9205dd5eda5c6f251a849 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 17 Dec 2019 18:46:19 +0100 Subject: [PATCH 14/61] Initial sync, start the sync thread or the sync service --- .../network/NetworkConnectivityChecker.kt | 2 +- .../android/internal/session/sync/SyncTask.kt | 1 - .../internal/session/sync/job/SyncService.kt | 25 ++++++++++++++++--- .../util/BackgroundDetectionObserver.kt | 6 ++--- .../vector/riotx/core/extensions/Session.kt | 7 +++--- .../riotx/core/services/VectorSyncService.kt | 23 ++++++++--------- .../im/vector/riotx/features/MainActivity.kt | 12 ++++----- vector/src/main/res/values/strings_riotX.xml | 2 ++ 8 files changed, 48 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt index e6dda3f5e1..6565b8685b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt @@ -53,7 +53,7 @@ internal class NetworkConnectivityChecker @Inject constructor(private val contex @WorkerThread fun hasInternetAccess(): Boolean { // If we are in background we have unbound merlin, so we have to check - return if (backgroundDetectionObserver.isIsBackground) { + return if (backgroundDetectionObserver.isInBackground) { merlinsBeard.hasInternetAccess() } else { hasInternetAccess diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 0914c35854..189c80d657 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -38,7 +38,6 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, @UserId private val userId: String, private val filterRepository: FilterRepository, private val syncResponseHandler: SyncResponseHandler, - private val sessionParamsStore: SessionParamsStore, private val initialSyncProgressService: DefaultInitialSyncProgressService, private val syncTokenStore: SyncTokenStore, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 0015308c62..37bcc225c1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -19,11 +19,13 @@ import android.app.Service import android.content.Intent import android.os.IBinder import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.isTokenError +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.util.BackgroundDetectionObserver import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.* import timber.log.Timber @@ -40,10 +42,13 @@ abstract class SyncService : Service() { private var userId: String? = null private var mIsSelfDestroyed: Boolean = false + private var isInitialSync: Boolean = false + private lateinit var session: Session private lateinit var syncTask: SyncTask private lateinit var networkConnectivityChecker: NetworkConnectivityChecker private lateinit var taskExecutor: TaskExecutor private lateinit var coroutineDispatchers: MatrixCoroutineDispatchers + private lateinit var backgroundDetectionObserver: BackgroundDetectionObserver private val isRunning = AtomicBoolean(false) @@ -52,14 +57,19 @@ abstract class SyncService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.i("onStartCommand $intent") intent?.let { + val matrix = Matrix.getInstance(applicationContext) val safeUserId = it.getStringExtra(EXTRA_USER_ID) ?: return@let - val sessionComponent = Matrix.getInstance(applicationContext).sessionManager.getSessionComponent(safeUserId) + val sessionComponent = matrix.sessionManager.getSessionComponent(safeUserId) ?: return@let + session = sessionComponent.session() userId = safeUserId syncTask = sessionComponent.syncTask() + isInitialSync = !session.hasAlreadySynced() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() coroutineDispatchers = sessionComponent.coroutineDispatchers() + backgroundDetectionObserver = matrix.backgroundDetectionObserver + onStart(isInitialSync) if (isRunning.get()) { Timber.i("Received a start while was already syncing... ignore") } else { @@ -92,7 +102,7 @@ abstract class SyncService : Service() { if (!networkConnectivityChecker.hasInternetAccess()) { Timber.v("No network reschedule to avoid wasting resources") userId?.also { - onRescheduleAsked(it, delay = 10_000L) + onRescheduleAsked(it, isInitialSync, delay = 10_000L) } stopMe() return @@ -101,6 +111,11 @@ abstract class SyncService : Service() { val params = SyncTask.Params(TIME_OUT) try { syncTask.execute(params) + // Start sync if we were doing an initial sync and the syncThread is not launched yet + if (isInitialSync && session.syncState().value == SyncState.Idle) { + val isForeground = !backgroundDetectionObserver.isInBackground + session.startSync(isForeground) + } stopMe() } catch (throwable: Throwable) { Timber.e(throwable) @@ -114,7 +129,9 @@ abstract class SyncService : Service() { } } - abstract fun onRescheduleAsked(userId: String, delay: Long) + abstract fun onStart(isInitialSync: Boolean) + + abstract fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) override fun onBind(intent: Intent?): IBinder? { return null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt index d89b732a0d..b8de50b9fc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/BackgroundDetectionObserver.kt @@ -29,7 +29,7 @@ import javax.inject.Inject @MatrixScope internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver { - var isIsBackground: Boolean = false + var isInBackground: Boolean = false private set private @@ -46,14 +46,14 @@ internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObse @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onMoveToForeground() { Timber.v("App returning to foreground…") - isIsBackground = false + isInBackground = false listeners.forEach { it.onMoveToForeground() } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onMoveToBackground() { Timber.v("App going to background…") - isIsBackground = true + isInBackground = true listeners.forEach { it.onMoveToBackground() } } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index 02615bdf1c..620e32f51f 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -55,10 +55,11 @@ fun Session.startSyncing(context: Context) { Timber.e(ex) } } + } else { + val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) + Timber.v("--> is at least started? $isAtLeastStarted") + startSync(isAtLeastStarted) } - val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) - Timber.v("--> is at least started? $isAtLeastStarted") - startSync(isAtLeastStarted) } /** diff --git a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index 93c08c50d6..31b56c4e47 100644 --- a/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -25,7 +25,6 @@ import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.riotx.R import im.vector.riotx.core.extensions.vectorComponent import im.vector.riotx.features.notifications.NotificationUtils -import timber.log.Timber class VectorSyncService : SyncService() { @@ -45,16 +44,20 @@ class VectorSyncService : SyncService() { notificationUtils = vectorComponent().notificationUtils() } - /** - * Service is started in fdroid mode when no FCM is available or is used for initialSync - */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Timber.v("VectorSyncService - onStartCommand ") + override fun onStart(isInitialSync: Boolean) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val notification = notificationUtils.buildForegroundServiceNotification(R.string.notification_listening_for_events, false) + val notificationSubtitleRes = if (isInitialSync) { + R.string.notification_initial_sync + } else { + R.string.notification_listening_for_events + } + val notification = notificationUtils.buildForegroundServiceNotification(notificationSubtitleRes, false) startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) } - return super.onStartCommand(intent, flags, startId) + } + + override fun onRescheduleAsked(userId: String, isInitialSync: Boolean, delay: Long) { + reschedule(userId, delay) } override fun onDestroy() { @@ -62,10 +65,6 @@ class VectorSyncService : SyncService() { super.onDestroy() } - override fun onRescheduleAsked(userId: String, delay: Long) { - reschedule(userId, delay) - } - private fun removeForegroundNotif() { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) diff --git a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt index 1c8ae3f859..26ecd25178 100644 --- a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt @@ -127,7 +127,8 @@ class MainActivity : VectorBaseActivity() { override fun onSuccess(data: Unit) { Timber.w("SIGN_OUT: success, start app") sessionHolder.clearActiveSession() - doLocalCleanupAndStart() + doLocalCleanup() + startNextActivityAndFinish() } override fun onFailure(failure: Throwable) { @@ -137,8 +138,9 @@ class MainActivity : VectorBaseActivity() { args.clearCache -> session.clearCache( object : MatrixCallback { override fun onSuccess(data: Unit) { + doLocalCleanup() session.startSyncing(applicationContext) - doLocalCleanupAndStart() + startNextActivityAndFinish() } override fun onFailure(failure: Throwable) { @@ -153,7 +155,7 @@ class MainActivity : VectorBaseActivity() { Timber.w("Ignoring invalid token global error") } - private fun doLocalCleanupAndStart() { + private fun doLocalCleanup() { GlobalScope.launch(Dispatchers.Main) { // On UI Thread Glide.get(this@MainActivity).clearMemory() @@ -165,8 +167,6 @@ class MainActivity : VectorBaseActivity() { deleteAllFiles(this@MainActivity.cacheDir) } } - - startNextActivityAndFinish() } private fun displayError(failure: Throwable) { @@ -182,7 +182,7 @@ class MainActivity : VectorBaseActivity() { private fun startNextActivityAndFinish() { val intent = when { args.clearCredentials - && !args.isUserLoggedOut -> + && !args.isUserLoggedOut -> // User has explicitly asked to log out LoginActivity.newIntent(this, null) args.isSoftLogout -> diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index c9180c3878..1e1eabe602 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -162,4 +162,6 @@ The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by RiotX.\nPlease first clear data, then sign in again on another account. Your matrix.to link was malformed + + Initial Sync… From 08970ad8c1beb1bf33c9f125ad91af6e94593ca8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 09:56:58 +0100 Subject: [PATCH 15/61] Fix a crash on public room list It's maybe a workaround, as it should not happen, but at least it will not crash anymore --- .../riotx/features/roomdirectory/RoomDirectoryViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index d89f0e2b99..dcd64c6a46 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -178,7 +178,9 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: copy( asyncPublicRoomsRequest = Success(data.chunk!!), // It's ok to append at the end of the list, so I use publicRooms.size() - publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size), + publicRooms = publicRooms.appendAt(data.chunk!!, publicRooms.size) + // Rageshake #8206 tells that we can have several times the same room + .distinctBy { it.roomId }, hasMore = since != null ) } From 9d26ba3186e63ba6f7e7202bda81198f83d430dd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 10:57:00 +0100 Subject: [PATCH 16/61] Fix rendering issue with HTML formatted body --- CHANGES.md | 1 + vector/build.gradle | 1 + .../src/main/assets/open_source_licenses.html | 5 +++ .../vector/riotx/core/di/VectorComponent.kt | 3 ++ .../action/MessageActionsViewModel.kt | 10 ++++- .../timeline/factory/MessageItemFactory.kt | 6 ++- .../features/html/VectorHtmlCompressor.kt | 40 +++++++++++++++++++ 7 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt diff --git a/CHANGES.md b/CHANGES.md index 5f83727cd7..50279bcf0e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Bugfix 🐛: - Scroll breadcrumbs to top when opened - Render default room name when it starts with an emoji (#477) - Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444 + - Fix rendering issue with HTML formatted body Translations 🗣: - diff --git a/vector/build.gradle b/vector/build.gradle index 8a2df7c120..de15a67fbd 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -293,6 +293,7 @@ dependencies { implementation 'me.gujun.android:span:1.7' implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:html:$markwon_version" + implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.4' implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'com.google.android:flexbox:1.1.1' implementation "androidx.autofill:autofill:$autofill_version" diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 3cd500138a..2b08270c89 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -359,6 +359,11 @@ SOFTWARE.
Copyright 2018 Kumar Bibek +
  • + htmlcompressor +
    + Copyright 2017 Sergiy Kovalchuk +
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
    index c8c5d697f6..b78e291506 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
    @@ -38,6 +38,7 @@ import im.vector.riotx.features.home.AvatarRenderer
     import im.vector.riotx.features.home.HomeRoomListDataSource
     import im.vector.riotx.features.home.group.SelectedGroupDataSource
     import im.vector.riotx.features.html.EventHtmlRenderer
    +import im.vector.riotx.features.html.VectorHtmlCompressor
     import im.vector.riotx.features.navigation.Navigator
     import im.vector.riotx.features.notifications.*
     import im.vector.riotx.features.rageshake.BugReporter
    @@ -87,6 +88,8 @@ interface VectorComponent {
     
         fun eventHtmlRenderer(): EventHtmlRenderer
     
    +    fun vectorHtmlCompressor(): VectorHtmlCompressor
    +
         fun navigator(): Navigator
     
         fun errorFormatter(): ErrorFormatter
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    index 102412948b..3c7e4624fe 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    @@ -41,6 +41,7 @@ import im.vector.riotx.core.resources.StringProvider
     import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
     import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
     import im.vector.riotx.features.html.EventHtmlRenderer
    +import im.vector.riotx.features.html.VectorHtmlCompressor
     import java.text.SimpleDateFormat
     import java.util.*
     
    @@ -82,6 +83,7 @@ data class MessageActionState(
     class MessageActionsViewModel @AssistedInject constructor(@Assisted
                                                               initialState: MessageActionState,
                                                               private val eventHtmlRenderer: Lazy,
    +                                                          private val htmlCompressor: VectorHtmlCompressor,
                                                               private val session: Session,
                                                               private val noticeEventFormatter: NoticeEventFormatter,
                                                               private val stringProvider: StringProvider
    @@ -170,8 +172,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
                 EventType.MESSAGE     -> {
                     val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
                     if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
    -                    eventHtmlRenderer.get().render(messageContent.formattedBody
    -                            ?: messageContent.body)
    +                    val html = messageContent.formattedBody
    +                            ?.takeIf { it.isNotBlank() }
    +                            ?.let { htmlCompressor.compress(it) }
    +                            ?: messageContent.body
    +
    +                    eventHtmlRenderer.get().render(html)
                     } else {
                         messageContent?.body
                     }
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index 9c96f17022..30f4e94cfb 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -46,6 +46,7 @@ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMoveme
     import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
     import im.vector.riotx.features.html.CodeVisitor
     import im.vector.riotx.features.html.EventHtmlRenderer
    +import im.vector.riotx.features.html.VectorHtmlCompressor
     import im.vector.riotx.features.media.ImageContentRenderer
     import im.vector.riotx.features.media.VideoContentRenderer
     import me.gujun.android.span.span
    @@ -57,6 +58,7 @@ class MessageItemFactory @Inject constructor(
             private val dimensionConverter: DimensionConverter,
             private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
             private val htmlRenderer: Lazy,
    +        private val htmlCompressor: VectorHtmlCompressor,
             private val stringProvider: StringProvider,
             private val imageContentRenderer: ImageContentRenderer,
             private val messageInformationDataFactory: MessageInformationDataFactory,
    @@ -227,6 +229,7 @@ class MessageItemFactory @Inject constructor(
                                             attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
             val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
             return if (isFormatted) {
    +            // First detect if the message contains some code block(s) or inline code
                 val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
                 val codeVisitor = CodeVisitor()
                 codeVisitor.visit(localFormattedBody)
    @@ -240,7 +243,8 @@ class MessageItemFactory @Inject constructor(
                         buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
                     }
                     CodeVisitor.Kind.NONE   -> {
    -                    val formattedBody = htmlRenderer.get().render(messageContent.formattedBody!!)
    +                    val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
    +                    val formattedBody = htmlRenderer.get().render(compressed)
                         buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
                     }
                 }
    diff --git a/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt b/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt
    new file mode 100644
    index 0000000000..9f3cf96a7e
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt
    @@ -0,0 +1,40 @@
    +/*
    + * 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.riotx.features.html
    +
    +import com.googlecode.htmlcompressor.compressor.Compressor
    +import com.googlecode.htmlcompressor.compressor.HtmlCompressor
    +import javax.inject.Inject
    +import javax.inject.Singleton
    +
    +@Singleton
    +class VectorHtmlCompressor @Inject constructor() {
    +
    +    // All default options are suitable so far
    +    private val htmlCompressor: Compressor = HtmlCompressor()
    +
    +    fun compress(html: String): String {
    +        var result = htmlCompressor.compress(html)
    +
    +        // Trim space after 
    and

    , unfortunately the method setRemoveSurroundingSpaces() from the doc does not exist + result = result.replace("
    ", "
    ") + result = result.replace("
    ", "
    ") + result = result.replace("

    ", "

    ") + + return result + } +} From c48a439eea00dd75f508758006283df2aa9121a0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 16:03:10 +0100 Subject: [PATCH 17/61] Add @JvmStatic for performance reasons. See https://github.com/airbnb/MvRx/wiki/Advanced-Concepts#mvrxviewmodel --- .../home/room/detail/timeline/action/MessageActionsViewModel.kt | 1 + .../room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt | 1 + .../room/detail/timeline/reactions/ViewReactionsViewModel.kt | 1 + .../home/room/list/actions/RoomListQuickActionsViewModel.kt | 1 + .../riotx/features/reactions/EmojiSearchResultViewModel.kt | 1 + .../vector/riotx/features/settings/push/PushGatewaysViewModel.kt | 1 + 6 files changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 3c7e4624fe..2de1fb225d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -102,6 +102,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted val quickEmojis = listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀") + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: MessageActionState): MessageActionsViewModel? { val fragment: MessageActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() return fragment.messageActionViewModelFactory.create(state) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt index c1cccbef7a..64d8950420 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt @@ -61,6 +61,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted companion object : MvRxViewModelFactory { + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? { val fragment: ViewEditHistoryBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() return fragment.viewEditHistoryViewModelFactory.create(state) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt index 9ec45b03b9..761e80dd59 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsViewModel.kt @@ -68,6 +68,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted companion object : MvRxViewModelFactory { + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionsViewModel? { val fragment: ViewReactionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() return fragment.viewReactionsViewModelFactory.create(state) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt index 7f7a1f41c4..1c4d414f18 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsViewModel.kt @@ -37,6 +37,7 @@ class RoomListQuickActionsViewModel @AssistedInject constructor(@Assisted initia companion object : MvRxViewModelFactory { + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomListQuickActionsState): RoomListQuickActionsViewModel? { val fragment: RoomListQuickActionsBottomSheet = (viewModelContext as FragmentViewModelContext).fragment() return fragment.roomListActionsViewModelFactory.create(state) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt index 057c5d8159..01debac5ed 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt @@ -42,6 +42,7 @@ class EmojiSearchResultViewModel @AssistedInject constructor( companion object : MvRxViewModelFactory { + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: EmojiSearchResultViewState): EmojiSearchResultViewModel? { val activity: EmojiReactionPickerActivity = (viewModelContext as ActivityViewModelContext).activity() return activity.emojiSearchResultViewModelFactory.create(state) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt index dd773f4c22..db4586dff5 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/push/PushGatewaysViewModel.kt @@ -40,6 +40,7 @@ class PushGatewaysViewModel @AssistedInject constructor(@Assisted initialState: companion object : MvRxViewModelFactory { + @JvmStatic override fun create(viewModelContext: ViewModelContext, state: PushGatewayViewState): PushGatewaysViewModel? { val fragment: PushGatewaysFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.pushGatewaysViewModelFactory.create(state) From 7697278bb2ef539e5aaa0987ece51fc9844ade1d Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 18 Dec 2019 16:59:45 +0100 Subject: [PATCH 18/61] LiveObservers: launch directly coroutines --- .../database/RealmLiveEntityObserver.kt | 10 +++++++ .../internal/session/DefaultSession.kt | 29 ++++++++++--------- .../session/group/GroupSummaryUpdater.kt | 28 ++++++++++-------- .../room/EventRelationsAggregationUpdater.kt | 10 +++---- .../create/RoomCreateEventLiveObserver.kt | 25 ++++++++-------- .../session/room/prune/EventsPruner.kt | 12 ++++---- .../RoomTombstoneEventLiveObserver.kt | 29 ++++++++++--------- .../sync/UserAccountDataSyncHandler.kt | 1 - .../internal/session/sync/job/SyncThread.kt | 1 - 9 files changed, 80 insertions(+), 65 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt index 9c7a788b44..ee6d8e507c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmLiveEntityObserver.kt @@ -19,12 +19,16 @@ package im.vector.matrix.android.internal.database import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.util.createBackgroundHandler import io.realm.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference internal interface LiveEntityObserver { fun start() fun dispose() + fun cancelProcess() fun isStarted(): Boolean } @@ -35,6 +39,7 @@ internal abstract class RealmLiveEntityObserver(protected val r val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND") } + protected val observerScope = CoroutineScope(SupervisorJob()) protected abstract val query: Monarchy.Query private val isStarted = AtomicBoolean(false) private val backgroundRealm = AtomicReference() @@ -59,10 +64,15 @@ internal abstract class RealmLiveEntityObserver(protected val r backgroundRealm.getAndSet(null).also { it.close() } + observerScope.coroutineContext.cancelChildren() } } } + override fun cancelProcess() { + observerScope.coroutineContext.cancelChildren() + } + override fun isStarted(): Boolean { return isStarted.get() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 8d508ccc75..d52379eb6e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -85,19 +85,19 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy) : Session, - RoomService by roomService.get(), - RoomDirectoryService by roomDirectoryService.get(), - GroupService by groupService.get(), - UserService by userService.get(), - CryptoService by cryptoService.get(), - SignOutService by signOutService.get(), - FilterService by filterService.get(), - PushRuleService by pushRuleService.get(), - PushersService by pushersService.get(), - FileService by fileService.get(), - InitialSyncProgressService by initialSyncProgressService.get(), - SecureStorageService by secureStorageService.get(), - HomeServerCapabilitiesService by homeServerCapabilitiesService.get() { + RoomService by roomService.get(), + RoomDirectoryService by roomDirectoryService.get(), + GroupService by groupService.get(), + UserService by userService.get(), + CryptoService by cryptoService.get(), + SignOutService by signOutService.get(), + FilterService by filterService.get(), + PushRuleService by pushRuleService.get(), + PushersService by pushersService.get(), + FileService by fileService.get(), + InitialSyncProgressService by initialSyncProgressService.get(), + SecureStorageService by secureStorageService.get(), + HomeServerCapabilitiesService by homeServerCapabilitiesService.get() { private var isOpen = false @@ -173,13 +173,14 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se override fun clearCache(callback: MatrixCallback) { stopSync() stopAnyBackgroundSync() + liveEntityObservers.forEach { it.cancelProcess() } cacheService.get().clearCache(callback) } @Subscribe(threadMode = ThreadMode.MAIN) fun onGlobalError(globalError: GlobalError) { if (globalError is GlobalError.InvalidToken - && globalError.softLogout) { + && globalError.softLogout) { // Mark the token has invalid GlobalScope.launch(Dispatchers.IO) { sessionParamsStore.setTokenInvalid(myUserId) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt index 9eceb44417..553a0387c5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupSummaryUpdater.kt @@ -22,6 +22,7 @@ import androidx.work.WorkManager import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.RealmLiveEntityObserver +import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.model.GroupEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.query.where @@ -31,6 +32,7 @@ import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWor import im.vector.matrix.android.internal.worker.WorkerParamsFactory import io.realm.OrderedCollectionChangeSet import io.realm.RealmResults +import kotlinx.coroutines.launch import javax.inject.Inject private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" @@ -49,14 +51,19 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont .mapNotNull { results[it] } fetchGroupsData(modifiedGroupEntity - .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } - .map { it.groupId } - .toList()) + .filter { it.membership == Membership.JOIN || it.membership == Membership.INVITE } + .map { it.groupId } + .toList()) - deleteGroups(modifiedGroupEntity + modifiedGroupEntity .filter { it.membership == Membership.LEAVE } .map { it.groupId } - .toList()) + .toList() + .also { + observerScope.launch { + deleteGroups(it) + } + } } private fun fetchGroupsData(groupIds: List) { @@ -77,12 +84,9 @@ internal class GroupSummaryUpdater @Inject constructor(private val context: Cont /** * Delete the GroupSummaryEntity of left groups */ - private fun deleteGroups(groupIds: List) { - monarchy - .writeAsync { realm -> - GroupSummaryEntity.where(realm, groupIds) - .findAll() - .deleteAllFromRealm() - } + private suspend fun deleteGroups(groupIds: List) = awaitTransaction(monarchy.realmConfiguration) { realm -> + GroupSummaryEntity.where(realm, groupIds) + .findAll() + .deleteAllFromRealm() } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt index aadf1bfccf..4a14005fe9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt @@ -23,11 +23,10 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.di.UserId -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith import io.realm.OrderedCollectionChangeSet import io.realm.RealmConfiguration import io.realm.RealmResults +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -39,8 +38,7 @@ import javax.inject.Inject internal class EventRelationsAggregationUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, @UserId private val userId: String, - private val task: EventRelationsAggregationTask, - private val taskExecutor: TaskExecutor) : + private val task: EventRelationsAggregationTask) : RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { @@ -63,6 +61,8 @@ internal class EventRelationsAggregationUpdater @Inject constructor(@SessionData insertedDomains, userId ) - task.configureWith(params).executeBy(taskExecutor) + observerScope.launch { + task.execute(params) + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt index 010023596c..1553ddec04 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/RoomCreateEventLiveObserver.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent import im.vector.matrix.android.internal.database.RealmLiveEntityObserver +import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity @@ -30,9 +31,9 @@ import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import io.realm.OrderedCollectionChangeSet -import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmResults +import kotlinx.coroutines.launch import javax.inject.Inject internal class RoomCreateEventLiveObserver @Inject constructor(@SessionDatabase @@ -51,21 +52,21 @@ internal class RoomCreateEventLiveObserver @Inject constructor(@SessionDatabase } .toList() .also { - handleRoomCreateEvents(it) + observerScope.launch { + handleRoomCreateEvents(it) + } } } - private fun handleRoomCreateEvents(createEvents: List) = Realm.getInstance(realmConfiguration).use { - it.executeTransactionAsync { realm -> - for (event in createEvents) { - val createRoomContent = event.getClearContent().toModel() - val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: continue + private suspend fun handleRoomCreateEvents(createEvents: List) = awaitTransaction(realmConfiguration) { realm -> + for (event in createEvents) { + val createRoomContent = event.getClearContent().toModel() + val predecessorRoomId = createRoomContent?.predecessor?.roomId ?: continue - val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst() - ?: RoomSummaryEntity(predecessorRoomId) - predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED - realm.insertOrUpdate(predecessorRoomSummary) - } + val predecessorRoomSummary = RoomSummaryEntity.where(realm, predecessorRoomId).findFirst() + ?: RoomSummaryEntity(predecessorRoomId) + predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_JOINED + realm.insertOrUpdate(predecessorRoomSummary) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt index d66a2f6e57..b29d3210bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/EventsPruner.kt @@ -23,11 +23,10 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.configureWith import io.realm.OrderedCollectionChangeSet import io.realm.RealmConfiguration import io.realm.RealmResults +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -36,8 +35,7 @@ import javax.inject.Inject * As it will actually delete the content, it should be called last in the list of listener. */ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - private val pruneEventTask: PruneEventTask, - private val taskExecutor: TaskExecutor) : + private val pruneEventTask: PruneEventTask) : RealmLiveEntityObserver(realmConfiguration) { override val query = Monarchy.Query { EventEntity.types(it, listOf(EventType.REDACTION)) } @@ -50,7 +48,9 @@ internal class EventsPruner @Inject constructor(@SessionDatabase realmConfigurat .mapNotNull { results[it]?.asDomain() } .toList() - val params = PruneEventTask.Params(insertedDomains) - pruneEventTask.configureWith(params).executeBy(taskExecutor) + observerScope.launch { + val params = PruneEventTask.Params(insertedDomains) + pruneEventTask.execute(params) + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt index 301c383a6d..e5e538ae89 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/tombstone/RoomTombstoneEventLiveObserver.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.tombstone.RoomTombstoneContent import im.vector.matrix.android.internal.database.RealmLiveEntityObserver +import im.vector.matrix.android.internal.database.awaitTransaction import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity @@ -30,9 +31,9 @@ import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.di.SessionDatabase import io.realm.OrderedCollectionChangeSet -import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmResults +import kotlinx.coroutines.launch import javax.inject.Inject internal class RoomTombstoneEventLiveObserver @Inject constructor(@SessionDatabase @@ -51,24 +52,24 @@ internal class RoomTombstoneEventLiveObserver @Inject constructor(@SessionDataba } .toList() .also { - handleRoomTombstoneEvents(it) + observerScope.launch { + handleRoomTombstoneEvents(it) + } } } - private fun handleRoomTombstoneEvents(tombstoneEvents: List) = Realm.getInstance(realmConfiguration).use { - it.executeTransactionAsync { realm -> - for (event in tombstoneEvents) { - if (event.roomId == null) continue - val createRoomContent = event.getClearContent().toModel() - if (createRoomContent?.replacementRoom == null) continue + private suspend fun handleRoomTombstoneEvents(tombstoneEvents: List) = awaitTransaction(realmConfiguration) { realm -> + for (event in tombstoneEvents) { + if (event.roomId == null) continue + val createRoomContent = event.getClearContent().toModel() + if (createRoomContent?.replacementRoom == null) continue - val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst() - ?: RoomSummaryEntity(event.roomId) - if (predecessorRoomSummary.versioningState == VersioningState.NONE) { - predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED - } - realm.insertOrUpdate(predecessorRoomSummary) + val predecessorRoomSummary = RoomSummaryEntity.where(realm, event.roomId).findFirst() + ?: RoomSummaryEntity(event.roomId) + if (predecessorRoomSummary.versioningState == VersioningState.NONE) { + predecessorRoomSummary.versioningState = VersioningState.UPGRADED_ROOM_NOT_JOINED } + realm.insertOrUpdate(predecessorRoomSummary) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index 3deb612756..35988e6c6f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -95,7 +95,6 @@ internal class UserAccountDataSyncHandler @Inject constructor(private val monarc } } - private fun handlePushRules(realm: Realm, userAccountDataPushRules: UserAccountDataPushRules) { val pushRules = userAccountDataPushRules.content realm.where(PushRulesEntity::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index 8b82e13d74..c550d40e1e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import com.squareup.moshi.JsonEncodingException import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.isTokenError import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.internal.network.NetworkConnectivityChecker From 123ffe9f9c7c7dfb0ca65c5103d67df4b957b55a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 17:00:18 +0100 Subject: [PATCH 19/61] Cleanup --- .../riotx/features/home/group/GroupListViewModel.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt index bbeda127fc..24318bc508 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt @@ -68,14 +68,14 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro } private fun observeSelectionState() { - selectSubscribe(GroupListViewState::selectedGroup) { - if (it != null) { + selectSubscribe(GroupListViewState::selectedGroup) { groupSummary -> + if (groupSummary != null) { val selectedGroup = _openGroupLiveData.value?.peekContent() - // We only wan to open group if the updated selectedGroup is a different one. - if (selectedGroup?.groupId != it.groupId) { - _openGroupLiveData.postLiveEvent(it) + // We only want to open group if the updated selectedGroup is a different one. + if (selectedGroup?.groupId != groupSummary.groupId) { + _openGroupLiveData.postLiveEvent(groupSummary) } - val optionGroup = Option.fromNullable(it) + val optionGroup = Option.just(groupSummary) selectedGroupStore.post(optionGroup) } } From 7eae85a39493bf0c851dc401ccd5ac09667b092f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 18:41:46 +0100 Subject: [PATCH 20/61] Add a ZeroItem to avoid automatic scroll when the breadcrumbs are updated from another client --- .../im/vector/riotx/core/epoxy/ZeroItem.kt | 30 +++++++++++++++++++ .../room/breadcrumbs/BreadcrumbsController.kt | 7 ++++- vector/src/main/res/layout/item_zero.xml | 4 +++ 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/epoxy/ZeroItem.kt create mode 100644 vector/src/main/res/layout/item_zero.xml diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/ZeroItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/ZeroItem.kt new file mode 100644 index 0000000000..b64abdcc6c --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/ZeroItem.kt @@ -0,0 +1,30 @@ +/* + * 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.riotx.core.epoxy + +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R + +/** + * Item of size (0, 0). + * It can be useful to avoid automatic scroll of RecyclerView with Epoxy controller, when the first valuable item changes. + */ +@EpoxyModelClass(layout = R.layout.item_zero) +abstract class ZeroItem : VectorEpoxyModel() { + + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt index bfc91bf5a1..3b77835917 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt @@ -19,6 +19,7 @@ package im.vector.riotx.features.home.room.breadcrumbs import android.view.View import com.airbnb.epoxy.EpoxyController import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.core.epoxy.zeroItem import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject @@ -45,9 +46,13 @@ class BreadcrumbsController @Inject constructor( override fun buildModels() { val safeViewState = viewState ?: return + // Add a ZeroItem to avoid automatic scroll when the breadcrumbs are updated from another client + zeroItem { + id("top") + } + // An empty breadcrumbs list can only be temporary because when entering in a room, // this one is added to the breadcrumbs - safeViewState.asyncBreadcrumbs.invoke() ?.forEach { breadcrumbsItem { diff --git a/vector/src/main/res/layout/item_zero.xml b/vector/src/main/res/layout/item_zero.xml new file mode 100644 index 0000000000..ec7c4e3f98 --- /dev/null +++ b/vector/src/main/res/layout/item_zero.xml @@ -0,0 +1,4 @@ + + From 648691656a9e0dff24001126f4bb624cdb72058a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 19:20:44 +0100 Subject: [PATCH 21/61] Disable click on Stickers (#703) --- CHANGES.md | 1 + .../detail/timeline/factory/MessageItemFactory.kt | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 50279bcf0e..133dba8909 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Bugfix 🐛: - Render default room name when it starts with an emoji (#477) - Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444 - Fix rendering issue with HTML formatted body + - Disable click on Stickers (#703) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 30f4e94cfb..c2b2b67129 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -181,10 +181,14 @@ class MessageItemFactory @Inject constructor( .playable(messageContent.info?.mimeType == "image/gif") .highlighted(highlight) .mediaData(data) - .clickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onImageMessageClicked(messageContent, data, view) - })) + .apply { + if (messageContent.type != MessageType.MSGTYPE_STICKER_LOCAL) { + clickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onImageMessageClicked(messageContent, data, view) + })) + } + } } private fun buildVideoMessageItem(messageContent: MessageVideoContent, From b0ff2cb4bb283cf2ed594987ed0234b3d5a5d1f7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 18 Dec 2019 19:31:10 +0100 Subject: [PATCH 22/61] cleanup --- .../vector/riotx/features/permalink/PermalinkHandlerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandlerActivity.kt b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandlerActivity.kt index 08e09fa48d..5339a2c6f9 100644 --- a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandlerActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandlerActivity.kt @@ -27,7 +27,6 @@ import im.vector.riotx.core.utils.toast import im.vector.riotx.features.home.LoadingFragment import im.vector.riotx.features.login.LoginActivity import io.reactivex.android.schedulers.AndroidSchedulers -import kotlinx.android.synthetic.debug.activity_test_material_theme.* import java.util.concurrent.TimeUnit import javax.inject.Inject From c8f0c83cd3b356e7af6af9244ae1413ec0db63db Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Dec 2019 10:38:10 +0100 Subject: [PATCH 23/61] Timeline: don't retry automatically to avoid totally blocking pagination --- .../internal/session/room/timeline/DefaultTimeline.kt | 3 ++- .../home/room/detail/timeline/TimelineEventController.kt | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 693855edbc..f42e50bdab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -504,7 +504,6 @@ internal class DefaultTimeline( Timber.v("Should fetch $limit items $direction") cancelableBag += paginationTask .configureWith(params) { - this.retryCount = Int.MAX_VALUE this.constraints = TaskConstraints(connectedToNetwork = true) this.callback = object : MatrixCallback { override fun onSuccess(data: TokenChunkEventPersistor.Result) { @@ -524,6 +523,8 @@ internal class DefaultTimeline( } override fun onFailure(failure: Throwable) { + updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } + postSnapshot() Timber.v("Failure fetching $limit items $direction from pagination request") } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 576b9fa0ba..0f187fa9e5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -220,7 +220,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec showingForwardLoader = LoadingItem_() .id("forward_loading_item_$timestamp") .setVisibilityStateChangedListener(Timeline.Direction.FORWARDS) - .addWhen(Timeline.Direction.FORWARDS) + .addWhenLoading(Timeline.Direction.FORWARDS) val timelineModels = getModels() add(timelineModels) @@ -230,7 +230,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec LoadingItem_() .id("backward_loading_item_$timestamp") .setVisibilityStateChangedListener(Timeline.Direction.BACKWARDS) - .addWhen(Timeline.Direction.BACKWARDS) + .addWhenLoading(Timeline.Direction.BACKWARDS) } } @@ -247,6 +247,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec currentSnapshot = newSnapshot val diffResult = DiffUtil.calculateDiff(diffCallback) diffResult.dispatchUpdatesTo(listUpdateCallback) + requestDelayedModelBuild(100) inSubmitList = false } } @@ -319,7 +320,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec /** * Return true if added */ - private fun LoadingItem_.addWhen(direction: Timeline.Direction): Boolean { + private fun LoadingItem_.addWhenLoading(direction: Timeline.Direction): Boolean { val shouldAdd = timeline?.hasMoreToLoad(direction) ?: false addIf(shouldAdd, this@TimelineEventController) return shouldAdd From 5bde7b9f178f7d4f0e40e495604f8083a32705d1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Dec 2019 10:37:31 +0100 Subject: [PATCH 24/61] Read marker: fix banner visibility when following permalink --- .../features/home/room/detail/RoomDetailViewModel.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index e7a18753cd..614a75265d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -819,9 +819,14 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro if (events.isEmpty()) return UnreadState.Unknown val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown val firstDisplayableEventId = timeline.getFirstDisplayableEventId(readMarkerIdSnapshot) - ?: return UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) val firstDisplayableEventIndex = timeline.getIndexOfEvent(firstDisplayableEventId) - ?: return UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) + if (firstDisplayableEventId == null || firstDisplayableEventIndex == null) { + return if (timeline.isLive) { + UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) + } else { + UnreadState.Unknown + } + } for (i in (firstDisplayableEventIndex - 1) downTo 0) { val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown From 6652965e481c22015be97a01ea6f2a79312c2912 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 10:46:11 +0100 Subject: [PATCH 25/61] Ignore lint issue --- vector/lint.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/lint.xml b/vector/lint.xml index b6da88aedd..6a9b0634a7 100644 --- a/vector/lint.xml +++ b/vector/lint.xml @@ -31,4 +31,8 @@ + + + + From 4b0dfa49f4429369f5f90f11e2f90b438e750efd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 11:44:07 +0100 Subject: [PATCH 26/61] Limit sticker size in the timeline --- CHANGES.md | 1 + .../session/room/timeline/TimelineEvent.kt | 4 +-- .../timeline/factory/MessageItemFactory.kt | 4 ++- .../timeline/item/MessageImageVideoItem.kt | 4 ++- .../features/media/ImageContentRenderer.kt | 30 +++++++++++++------ 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 133dba8909..eef8808393 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Features ✨: Improvements 🙌: - Handle navigation to room via room alias (#201) - Open matrix.to link in RiotX (#57) + - Limit sticker size in the timeline Other changes: - Use same default room colors than Riot-Web diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index ed7f49aa46..caa64a85f8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -104,7 +104,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { root.getClearContent().toModel() } else { annotations?.editSummary?.aggregatedContent?.toModel() - ?: root.getClearContent().toModel() + ?: root.getClearContent().toModel() } } @@ -116,7 +116,7 @@ fun TimelineEvent.getLastMessageBody(): String? { if (lastMessageContent != null) { return lastMessageContent.newContent?.toModel()?.body - ?: lastMessageContent.body + ?: lastMessageContent.body } return null diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index c2b2b67129..9e05cdcc18 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -182,7 +182,9 @@ class MessageItemFactory @Inject constructor( .highlighted(highlight) .mediaData(data) .apply { - if (messageContent.type != MessageType.MSGTYPE_STICKER_LOCAL) { + if (messageContent.type == MessageType.MSGTYPE_STICKER_LOCAL) { + mode(ImageContentRenderer.Mode.STICKER) + } else { clickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onImageMessageClicked(messageContent, data, view) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 457f30cbf4..2fd46ddf12 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -36,6 +36,8 @@ abstract class MessageImageVideoItem : AbsMessageItem contentUrlResolver.resolveFullSize(data.url) + Mode.FULL_SIZE, + Mode.STICKER -> contentUrlResolver.resolveFullSize(data.url) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) } // Fallback to base url @@ -149,18 +152,27 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: val maxImageHeight = data.maxHeight val width = data.width ?: maxImageWidth val height = data.height ?: maxImageHeight - var finalHeight = -1 var finalWidth = -1 + var finalHeight = -1 // if the image size is known // compute the expected height if (width > 0 && height > 0) { - if (mode == Mode.FULL_SIZE) { - finalHeight = height - finalWidth = width - } else { - finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight) - finalWidth = finalHeight * width / height + when (mode) { + Mode.FULL_SIZE -> { + finalHeight = height + finalWidth = width + } + Mode.THUMBNAIL -> { + finalHeight = min(maxImageWidth * height / width, maxImageHeight) + finalWidth = finalHeight * width / height + } + Mode.STICKER -> { + // limit on width + val maxWidthDp = min(dimensionConverter.dpToPx(120), maxImageWidth / 2) + finalWidth = min(dimensionConverter.dpToPx(width), maxWidthDp) + finalHeight = finalWidth * height / width + } } } // ensure that some values are properly initialized From bb9510e59b7bf7abe2ddc7b7c2d92533c0282d45 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 12:05:30 +0100 Subject: [PATCH 27/61] Create Size data class --- .../im/vector/riotx/core/ui/model/Size.kt | 20 ++++++++++++++++++ .../features/media/ImageContentRenderer.kt | 21 ++++++++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt diff --git a/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt b/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt new file mode 100644 index 0000000000..65ab0ad2b2 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/ui/model/Size.kt @@ -0,0 +1,20 @@ +/* + * 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.riotx.core.ui.model + +// android.util.Size in API 21+ +data class Size(val width: Int, val height: Int) diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index 60abd19191..909fd5b8eb 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideRequest +import im.vector.riotx.core.ui.model.Size import im.vector.riotx.core.utils.DimensionConverter import im.vector.riotx.core.utils.isLocalFile import kotlinx.android.parcel.Parcelize @@ -62,13 +63,13 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: } fun render(data: Data, mode: Mode, imageView: ImageView) { - val (width, height) = processSize(data, mode) - imageView.layoutParams.height = height - imageView.layoutParams.width = width + val size = processSize(data, mode) + imageView.layoutParams.width = size.width + imageView.layoutParams.height = size.height // a11y imageView.contentDescription = data.filename - createGlideRequest(data, mode, imageView, width, height) + createGlideRequest(data, mode, imageView, size) .dontAnimate() .transform(RoundedCorners(dimensionConverter.dpToPx(8))) .thumbnail(0.3f) @@ -76,12 +77,12 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: } fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) { - val (width, height) = processSize(data, mode) + val size = processSize(data, mode) // a11y imageView.contentDescription = data.filename - createGlideRequest(data, mode, imageView, width, height) + createGlideRequest(data, mode, imageView, size) .listener(object : RequestListener { override fun onLoadFailed(e: GlideException?, model: Any?, @@ -104,7 +105,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: .into(imageView) } - private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, width: Int, height: Int): GlideRequest { + private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, size: Size): GlideRequest { return if (data.elementToDecrypt != null) { // Encrypted image GlideApp @@ -116,7 +117,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: val resolvedUrl = when (mode) { Mode.FULL_SIZE, Mode.STICKER -> contentUrlResolver.resolveFullSize(data.url) - Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) + Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE) } // Fallback to base url ?: data.url @@ -147,7 +148,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ) } - private fun processSize(data: Data, mode: Mode): Pair { + private fun processSize(data: Data, mode: Mode): Size { val maxImageWidth = data.maxWidth val maxImageHeight = data.maxHeight val width = data.width ?: maxImageWidth @@ -182,6 +183,6 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: if (finalWidth < 0) { finalWidth = maxImageWidth } - return Pair(finalWidth, finalHeight) + return Size(finalWidth, finalHeight) } } From bf69810f8f648099ca11828a428771d0cd73973f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 12:05:47 +0100 Subject: [PATCH 28/61] Bottom sheet event preview for Sticker --- .../room/detail/timeline/action/MessageActionsViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 2de1fb225d..1303c3aad9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -170,7 +170,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun computeMessageBody(timelineEvent: Async): CharSequence? { return when (timelineEvent()?.root?.getClearType()) { - EventType.MESSAGE -> { + EventType.MESSAGE, + EventType.STICKER -> { val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { val html = messageContent.formattedBody From a4ea9a09ad2fe44bc6f867d939d060be9a4fa44e Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 19 Dec 2019 13:41:57 +0100 Subject: [PATCH 29/61] Room factory: add scope to avoid recreate all the dependencies --- .../vector/matrix/android/internal/session/room/RoomFactory.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 30a2948f68..a21a3b4a8d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -20,6 +20,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper +import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService @@ -35,6 +36,7 @@ internal interface RoomFactory { fun create(roomId: String): Room } +@SessionScope internal class DefaultRoomFactory @Inject constructor(private val monarchy: Monarchy, private val roomSummaryMapper: RoomSummaryMapper, private val cryptoService: CryptoService, From 7bb8cb0682880010b00ef553f48d609464760125 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 19 Dec 2019 13:42:15 +0100 Subject: [PATCH 30/61] Permalink: fix nav to same room --- .../java/im/vector/riotx/features/permalink/PermalinkHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt index c849166738..41ddb49cb5 100644 --- a/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/permalink/PermalinkHandler.kt @@ -57,7 +57,7 @@ class PermalinkHandler @Inject constructor(private val session: Session, .observeOn(AndroidSchedulers.mainThread()) .map { val roomId = it.getOrNull() - if (navigateToRoomInterceptor?.navToRoom(roomId) != true) { + if (navigateToRoomInterceptor?.navToRoom(roomId, permalinkData.eventId) != true) { openRoom(context, roomId, permalinkData.eventId, buildTask) } true From 13439769a1cd51619dd5bde68cd0ecad3ad40958 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 14:01:58 +0100 Subject: [PATCH 31/61] Update wording --- vector/src/main/res/values/strings_riotX.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index f315109ddf..753a15274a 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -114,7 +114,7 @@ Sign up to %1$s - Username + Username or email Password Next That username is taken From 156cc1aa4ab517c6bfa430754958d21ffaa98b9d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 15:50:18 +0100 Subject: [PATCH 32/61] Import Strings from Riot --- .../src/main/res/values-cs/strings.xml | 82 ++ .../src/main/res/values-ko/strings.xml | 2 +- .../src/main/res/values-sk/strings.xml | 63 +- .../src/main/res/values-zh-rCN/strings.xml | 5 +- .../src/main/res/values/strings.xml | 14 + .../src/main/res/values/strings_RiotX.xml | 15 +- vector/src/main/res/values-bn-rIN/strings.xml | 118 +- vector/src/main/res/values-cs/strings.xml | 13 +- vector/src/main/res/values-de/strings.xml | 4 +- vector/src/main/res/values-es/strings.xml | 30 + vector/src/main/res/values-eu/strings.xml | 35 + vector/src/main/res/values-fi/strings.xml | 168 ++- vector/src/main/res/values-fr/strings.xml | 37 +- vector/src/main/res/values-hu/strings.xml | 36 + vector/src/main/res/values-id/strings.xml | 2 +- vector/src/main/res/values-it/strings.xml | 1203 +++++++++-------- vector/src/main/res/values-sq/strings.xml | 100 +- vector/src/main/res/values-zh-rCN/strings.xml | 9 + vector/src/main/res/values-zh-rTW/strings.xml | 39 +- vector/src/main/res/values/strings.xml | 164 ++- vector/src/main/res/values/strings_riotX.xml | 159 --- 21 files changed, 1445 insertions(+), 853 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values-cs/strings.xml b/matrix-sdk-android/src/main/res/values-cs/strings.xml index f6e23752c3..61f3db0b25 100644 --- a/matrix-sdk-android/src/main/res/values-cs/strings.xml +++ b/matrix-sdk-android/src/main/res/values-cs/strings.xml @@ -82,4 +82,86 @@ Prázdná místnost + %s upravil/a tuto místnost. + + Zpráva byla smazána [důvod: %1$s] + Zpráva smazána [smazal/a %1$s] [důvod: %2$s] + "%1$s obnovil/a pozvánku do místnosti pro %2$s" + Kočka + Lev + Kůň + Jednorožec + Prase + Slon + Králík + Panda + Kohout + Tučnák + Želva + Ryba + Chobotnice + Motýl + Květina + Strom + Kaktus + Houba + Glóbus + Měsíc + Mrak + Oheň + Banán + Jablko + Jahoda + Kukuřice + Pizza + Dort + Srdce + Smajlík + Robot + Klobouk + Brýle + Santa + Zvednutý palec + Deštník + Přesípací hodiny + Hodiny + Dárek + Žárovka + Knížka + Tužka + Sponka + Nůžky + Zámek + Klíč + Kladivo + Telefon + Vlajka + Vlak + Kolo + Letadlo + Raketa + Pohár + Míč + Kytara + Trumpeta + Zvon + Kotva + Sluchátka + Složka + Úvodní synchronizace: +\nStahuji účet… + Uvodní synchronizace: +\nStahuji klíče + Uvodní synchnizace: +\nStahuji místnost + Uvodní synchronizace: +\nStahuji moje místnosti + Uvodní synchonizace: +\nStahuji místnosti, které jsem opustil/a + Úvodní sychronizace: +\nImportuji komunity + Úvodní synchronizace: +\nImportuji data účtu + + Posílám zprávu… diff --git a/matrix-sdk-android/src/main/res/values-ko/strings.xml b/matrix-sdk-android/src/main/res/values-ko/strings.xml index 9dfbb6609b..68e94bb641 100644 --- a/matrix-sdk-android/src/main/res/values-ko/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ko/strings.xml @@ -49,7 +49,7 @@ %1$s님이 %2$s님에게 방 초대를 보냈습니다 %1$s님이 %2$s의 초대를 수락했습니다 - ** 암호를 해독할 수 없음: %s ** + ** 암호를 복호화할 수 없음: %s ** 발신인의 기기에서 이 메시지의 키를 보내지 않았습니다. 관련 대화 diff --git a/matrix-sdk-android/src/main/res/values-sk/strings.xml b/matrix-sdk-android/src/main/res/values-sk/strings.xml index d5c7d95fd6..b729932b1f 100644 --- a/matrix-sdk-android/src/main/res/values-sk/strings.xml +++ b/matrix-sdk-android/src/main/res/values-sk/strings.xml @@ -88,70 +88,70 @@ Správa odstránená používateľom %1$s Správa odstránená [dôvod: %1$s] Správa odstránená používateľom %1$s [dôvod: %2$s] - Pes - Mačka - Lev + Hlava psa + Hlava mačky + Hlava leva Kôň - Jednorožec - Prasa + Hlava jednorožca + Hlava prasaťa Slon - Zajac - Panda + Hlava zajaca + Hlava pandy Kohút Tučniak Korytnačka Ryba Chobotnica Motýľ - Kvetina - Strom + Tulipán + Listnatý strom Kaktus - Hríb + Huba Zemeguľa - Mesiac + Polmesiac Oblak Oheň Banán - Jablko + Červené jablko Jahoda - Kukurica + Kukuričný klas Pizza - Koláč - Srdce - Úsmev + Narodeninová torta + Červené + Škeriaca sa tvár Robot - Klobúk + Cylinder Okuliare - Skrutkovač - Mikuláš + Francúzsky kľúč + Santa Claus Palec nahor Dáždnik Presýpacie hodiny - Hodiny - Darček + Budík + Zabalený darček Žiarovka - Kniha + Zatvorená kniha Ceruzka - Kancelárska sponka + Sponka na papier Nožnice - Zámok + Zatvorená zámka Kľúč Kladivo Telefón - Vlajka - Vlak + Kockovaná zástava + Rušeň Bicykel Lietadlo Raketa Trofej - Lopta + Futbal Gitara Trúbka - Zvonček + Zvon Kotva - Schlúchadlá - Priečinok - Pin + Slúchadlá + Fascikel + Špendlík Úvodná synchronizácia: \nPrebieha import účtu… @@ -173,4 +173,5 @@ Odosielanie správy… Vymazať správy na odoslanie + %1$s zamietol pozvanie používateľa %2$s vstúpiť do miestnosti diff --git a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml index 3aed8858a3..6e3ced3048 100644 --- a/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml +++ b/matrix-sdk-android/src/main/res/values-zh-rCN/strings.xml @@ -13,7 +13,7 @@ %1$s 封禁了 %2$s %1$s 更换了他们的头像 %1$s 将他们的昵称设置为 %2$s - %1$s 把他们的昵称从 %2$s 改为 %3$s + %1$s 把他的昵称从 %2$s 改为 %3$s %1$s 移除了他们的昵称 (%2$s) %1$s 把主题改为: %2$s %1$s 把聊天室名称改为: %2$s @@ -167,4 +167,7 @@ 正在发送消息… 清除正在发送队列 + %1$s 撤回了对 %2$s 邀请 + 置顶 + diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index ce26c22137..e611ae25b0 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -243,4 +243,18 @@ Sending message… Clear sending queue + %1$s\'s invitation. Reason: %2$s + %1$s invited %2$s. Reason: %3$s + %1$s invited you. Reason: %2$s + %1$s joined. Reason: %2$s + %1$s left. Reason: %2$s + %1$s rejected the invitation. Reason: %2$s + %1$s kicked %2$s. Reason: %3$s + %1$s unbanned %2$s. Reason: %3$s + %1$s banned %2$s. Reason: %3$s + %1$s sent an invitation to %2$s to join the room. Reason: %3$s + %1$s revoked the invitation for %2$s to join the room. Reason: %3$s + %1$s accepted the invitation for %2$s. Reason: %3$s + %1$s withdrew %2$s\'s invitation. Reason: %3$s + diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index a22533c6d1..03bc6d3684 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -2,19 +2,6 @@ - %1$s\'s invitation. Reason: %2$s - %1$s invited %2$s. Reason: %3$s - %1$s invited you. Reason: %2$s - %1$s joined. Reason: %2$s - %1$s left. Reason: %2$s - %1$s rejected the invitation. Reason: %2$s - %1$s kicked %2$s. Reason: %3$s - %1$s unbanned %2$s. Reason: %3$s - %1$s banned %2$s. Reason: %3$s - %1$s sent an invitation to %2$s to join the room. Reason: %3$s - %1$s revoked the invitation for %2$s to join the room. Reason: %3$s - %1$s accepted the invitation for %2$s. Reason: %3$s - %1$s withdrew %2$s\'s invitation. Reason: %3$s - There is no network connection right now + \ No newline at end of file diff --git a/vector/src/main/res/values-bn-rIN/strings.xml b/vector/src/main/res/values-bn-rIN/strings.xml index 06ee50219e..a0af9661d4 100644 --- a/vector/src/main/res/values-bn-rIN/strings.xml +++ b/vector/src/main/res/values-bn-rIN/strings.xml @@ -336,12 +336,10 @@ ভিডিও কল সম্পাদনের জন্য Riot আপনার ক্যামেরা এবং আপনার মাইক্রোফোন অ্যাক্সেস করার অনুমতির প্রয়োজন। \n \nকল করতে সক্ষম হতে পরবর্তী পপ আপ অ্যাক্সেস অনুমতি দিন। - Riot আপনার ইমেল এবং ফোন নম্বরগুলির উপর ভিত্তি করে অন্যান্য ম্যাট্রিক্স ব্যবহারকারীদের খুঁজে পেতে আপনার ঠিকানা বই পরিচিতিগুলি অ্যাক্সেস করার অনুমতির প্রয়োজন। + রায়ট অন্যান্য ম্যাট্রিক্স ব্যবহারকারীদের তাদের ইমেল এবং ফোন নম্বরগুলির উপর ভিত্তি করে আপনার ঠিকানা বইটি চেক করতে পারে। আপনি যদি এই উদ্দেশ্যে আপনার ঠিকানা বইটি ভাগ করে নিতে সম্মত হন তবে দয়া করে পরবর্তী পপ-আপটিতে অ্যাক্সেসের অনুমতি দিন। + রায়ট অন্যান্য ম্যাট্রিক্স ব্যবহারকারীদের তাদের ইমেল এবং ফোন নম্বরগুলির উপর ভিত্তি করে আপনার ঠিকানা বইটি চেক করতে পারে। \n -\nদয়া করে পরবর্তী পপ-আপ অ্যাক্সেসের অনুমতি দিয়ে ঠিকানা বই ব্যবহারকারীদের Riot থেকে অ্যাক্সেসযোগ্য করুন। - Riot কে আপনার ইমেল এবং ফোন নম্বরগুলির উপর ভিত্তি করে অন্যান্য ম্যাট্রিক্স ব্যবহারকারীদের সন্ধান করার জন্য আপনার ঠিকানা বই যোগাযোগ অ্যাক্সেস করার অনুমতির প্রয়োজন। -\n -\nRiot কে আপনার পরিচিতি অ্যাক্সেস করার অনুমতি দেবেন \? +\nআপনি এই উদ্দেশ্যে আপনার ঠিকানা বই ভাগ করতে সম্মত হন\? "দুঃখিত। কর্ম সঞ্চালিত না, অনুপস্থিত অনুমতির জন্য " @@ -726,7 +724,7 @@ বুট করার সময় শুরু ব্যাকগ্রাউন্ড সিঙ্ক সক্ষম করুন সিঙ্ক অনুরোধ সময়সীমার - প্রতিটি অনুরোধের মধ্যে বিলম্ব + প্রতিটি সিঙ্কের মধ্যে বিলম্ব সেকেন্ড সেকেন্ড @@ -791,10 +789,10 @@ ডেটা সংরক্ষণ মোড ডেটা সংরক্ষণ মোড একটি নির্দিষ্ট ফিল্টার প্রয়োগ করে যাতে উপস্থিতি আপডেট এবং টাইপিং বিজ্ঞপ্তি ফিল্টার করা হয়। - যন্ত্র বিবরণ + যন্ত্রের তথ্য আইডি - নাম - যন্ত্রের নাম + সর্বজনীন নাম + সর্বজনীন নাম আপডেট করুন শেষ দেখা %1$s @ %2$s এই অপারেশন অতিরিক্ত প্রমাণীকরণ প্রয়োজন। @@ -942,9 +940,9 @@ ডিক্রিপশন সমস্যা প্রেরক ডিভাইস তথ্য - যন্ত্রের নাম - নাম - ডিভাইস আইডি + সর্বজনীন নাম + সর্বজনীন নাম + আইডি যন্ত্রের কুঞ্জি প্রতিপাদন Ed25519 ফিঙ্গারপ্রিন্ট @@ -1098,4 +1096,100 @@ কমান্ড \"%s\" আরও পেরামিটার প্রয়োজন, অথবা কিছু পেরামিটার ভুল। কর্ম প্রদর্শন করে দেওয়া আইডি সঙ্গে ব্যবহারকারী কে নিষিদ্ধ করে + কোনটা না + রদ কর + বিযুক্ত + পর্যালোচনা + পতন + + কোনও পরিচয় সার্ভার কনফিগার করা নেই। + + ভুল কনফিগার্ড সার্ভারের কারণে কল ব্যর্থ হয়েছে + কলগুলি নির্ভরযোগ্যভাবে কাজ করার জন্য দয়া করে আপনার হোমসার্ভার (%1$s) এর প্রশাসককে একটি টার্ন সার্ভার কনফিগার করতে বলুন। +\n +\nবিকল্পভাবে, আপনি পাবলিক সার্ভারটি %2$s এ ব্যবহার করার চেষ্টা করতে পারেন, তবে এটি নির্ভরযোগ্য হবে না এবং এটি আপনার সার্ভারের সাথে আপনার আইপি ঠিকানাটি ভাগ করে দেবে। আপনি সেটিংসে এটি পরিচালনা করতে পারেন। + %s ব্যবহার করার চেষ্টা করুন + আবার আমাকে জিজ্ঞাসা করবেন না + + অ্যাকাউন্ট পুনরুদ্ধারের জন্য একটি ইমেল সেট করুন এবং পরে আপনাকে চিনে এমন লোকেরা ইচ্ছিকভাবে আবিষ্কারযোগ্য। + একটি ফোন সেট করুন এবং পরে আপনাকে জারা চেনে সেই লোকেদের দ্বারা বিকল্প হিসাবে আবিষ্কার করার জন্য। + অ্যাকাউন্ট পুনরুদ্ধারের জন্য একটি ইমেল সেট করুন। আপনার পরিচিত লোকদের দ্বারা বিকল্প হিসাবে আবিষ্কারের জন্য পরে ইমেল বা ফোন ব্যবহার করুন। + অ্যাকাউন্ট পুনরুদ্ধারের জন্য একটি ইমেল সেট করুন। আপনার পরিচিত লোকদের দ্বারা বিকল্প হিসাবে আবিষ্কারের জন্য পরে ইমেল বা ফোন ব্যবহার করুন। + এই ইউআরএলে কোনও হোম সার্ভারে পৌঁছানো যায় না, দয়া করে এটি পরীক্ষা করে দেখুন + ফ্যালব্যাক কল সহায়তা সার্ভারকে অনুমতি দিন + আপনার হোমসার্ভার একটি প্রস্তাব না দিলে সহায়তা হিসাবে %s ব্যবহার করবে (আপনার আইপি ঠিকানা কল করার সময় ভাগ করা হবে) + এই ক্রিয়াটি সম্পাদন করতে আপনার সেটিংসে একটি পরিচয় সার্ভার যুক্ত করুন। + আপনার পাসওয়ার্ড নিশ্চিত করুন + আপনি রায়ট মোবাইল থেকে এটি করতে পারবেন না + প্রমাণীকরণ প্রয়োজন + + + পটভূমি সিঙ্ক মোড (পরীক্ষামূলক) + ব্যাটারির জন্য অনুকূলিত + রায়ট এমনভাবে পটভূমিতে সিঙ্ক হবে যা ডিভাইসের সীমিত সংস্থান (ব্যাটারি) সংরক্ষণ করে। +\nআপনার ডিভাইস রিসোর্স স্থিতির উপর নির্ভর করে সিঙ্কটি অপারেটিং সিস্টেম দ্বারা পিছিয়ে যেতে পারে। + রিয়েল টাইম জন্য অনুকূলিত + রায়ট নির্দিষ্ট সময়ে সময়ে পটভূমিতে সিঙ্ক হবে (কনফিগারযোগ্য)। +\nএটি রেডিও এবং ব্যাটারির ব্যবহারকে প্রভাবিত করবে, রায়ট ইভেন্টগুলি শুনছে বলে জানিয়ে একটি স্থায়ী বিজ্ঞপ্তি প্রদর্শিত হবে। + কোনও পটভূমি সিঙ্ক না + অ্যাপটি ব্যাকগ্রাউন্ডে থাকা অবস্থায় আপনাকে আগত বার্তাগুলি সম্পর্কে অবহিত করা হবে না। + সেটিংস আপডেট করতে ব্যর্থ। + + + পছন্দের সিঙ্ক ব্যবধান + %s +\nসিঙ্কটি ডিভাইসের সংস্থান (ব্যাটারি) বা অবস্থার (ঘুম) উপর নির্ভর করে পিছিয়ে যেতে পারে। + ঐক্যবদ্ধতা + বট, সেতু, উইজেট এবং স্টিকার প্যাকগুলি পরিচালনা করতে ইন্টিগ্রেশন ম্যানেজার ব্যবহার করুন। +\nইন্টিগ্রেশন ম্যানেজাররা কনফিগারেশন ডেটা গ্রহণ করে এবং উইজেটগুলি সংশোধন করতে, রুম আমন্ত্রন প্রেরণ করতে এবং আপনার পক্ষে পাওয়ার স্তর নির্ধারণ করতে পারে। + আবিষ্কার + আপনার আবিষ্কারের সেটিংস পরিচালনা করুন। + সংহতকরণের অনুমতি দিন + ইন্টিগ্রেশন ম্যানেজার + + সর্বজনীন নাম (যাদের সাথে আপনি যোগাযোগ করেন তাদের কাছে দৃশ্যমান) + একটি ডিভাইসের সর্বজনীন নাম আপনি যাদের সাথে যোগাযোগ করেন তাদের কাছে দৃশ্যমান + উইজেট + উইজেট লোড করুন + এই উইজেটটি যুক্ত করেছেন: + এটি ব্যবহার করে কুকিজ সেট করতে পারে এবং %s এর সাথে ডেটা ভাগ করা যায়: + এটি ব্যবহার করে ডেটা %s এর সাথে ভাগ করে নিতে পারে: + উইজেট লোড করতে ব্যর্থ। +\n%s + উইজেট পুনরায় লোড করুন + ব্রাউজারে খুলুন + আমার জন্য অ্যাক্সেস প্রত্যাহার করুন + + আপনার প্রদশনীয় নাম + আপনার অবতার URL + আপনার ব্যবহারকারীর আইডি + আপনার থিম + উইজেট আইডি + কক্ষের আইডি + + + এই উইজেটটি নিম্নলিখিত সংস্থানগুলি ব্যবহার করতে চায়: + অনুমতি + সব অবরুদ্ধ + ক্যামেরা ব্যবহার করুন + মাইক্রোফোন ব্যবহার করুন + ডিআরএম সুরক্ষিত মিডিয়া পড়ুন + + কোনও ইন্টিগ্রেশন ম্যানেজার কনফিগার করা নেই। + চালিয়ে যেতে আপনার এই পরিষেবার শর্তাদি স্বীকার করতে হবে। + + প্রদত্ত আইডি সহ ব্যবহারকারীকে নিষিদ্ধ তালিকা থেকে বের করে + ব্যবহারকারীর পাওয়ার স্তর নির্ধারণ করুন + প্রদত্ত আইডি সহ ব্যবহারকারীকে ডিওপ করে + প্রদত্ত আইডি সহ ব্যবহারকারীকে বর্তমান কক্ষ এ আমন্ত্রণ জানায় + প্রদত্ত ওরফে সহ রুমে যোগ দেয় + কক্ষ ছেড়ে দিন + রুমের টপিক সেট করুন + প্রদত্ত আইডি সহ ব্যবহারকারীকে কিক্ করে + আপনার প্রদর্শনের ডাকনাম পরিবর্তন করে + অন/অফ মার্কডাউন + মার্কডাউন সক্ষম করা হয়েছে। + মার্কডাউন অক্ষম করা হয়েছে। + + অফ diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 53f1d35d79..0cf8f9d8e9 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -28,7 +28,7 @@ CZ Zapomenout místnost - Synchronizace + Synchronizuji.. Hlasitá oznámení Tichá oznámení @@ -46,7 +46,8 @@ Zobraz dešifrovaný zdroj Nahlásit obsah Aktivní hovor - Probíhající konferenční hovor.\nPřipojit se %1$s nebo %2$s. + Probíhající konferenční hovor. +\nPřipojit se jako %1$s nebo %2$s. hlasem videem Nemohu spustit hovor, prosím zkusit později @@ -601,4 +602,12 @@ Omlouváme se za způsobené nepříjemnosti. Nastavit důležitost upozornění na základě události, nastavení zvuku, LED, vibrací Důležitost upozornění na základě události + Inicializuji službu + Ověřte zařízení + + Odpojit + Ignorovat + Odmítnout + + Označit za přečtené diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 47dc512cb4..55fa5d1488 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -1695,7 +1695,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Für andere auffindbar sein Verwenden Sie Bots, Bridges, Widgets und Sticker-Packs - Lesen Sie bei + Gelesen von Identitätsserver @@ -1708,4 +1708,6 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Gib einen neuen Identitätsserver ein Konnte keine Verbindung zum Heimserver herstellen + Latn + diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 252acefabc..fb2f023ef9 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -1568,4 +1568,34 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Haz una sugerencia 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 Riot 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. + Entérese aquí de los mensajes sin leer diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index 57eed05cca..046e5557f5 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -1766,4 +1766,39 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Ez dago sare konexiorik orain + Berretsi zure pasahitza + Ezin duzu hau egin mugikorrerako Riot erabiliz + Autentifikazioa behar da + + + Integrazioak + Erabili integrazio kudeatzaileren bat botak, zubiak, trepetak eta eranskailu multzoak kudeatzeko. +\nIntegrazio kudeatzaileek konfigurazio datuak jasotzen dituzte, eta trepetak aldatu ditzakete, gelarako gonbidapenak bidali, eta botere mailak zure izenean ezarri. + Baimendu integrazioak + Trepeta + Kargatu trepeta + Trepeta hau honek gehitu du: + Hau erabiltzean cookie-ak ezarri litezke eta %s zerbitzariarekin datuak partekatu: + Hau erabiltzean %s zerbitzariarekin datuak partekatu litezke: + Huts egin du trepeta kargatzean. +\n%s + Birkargatu trepeta + Ireki nabigatzailean + Indargabetu sarbidea niretzat + + Zure pantaila-izena + Zure abatarraren URL-a + Zure erabiltzaile ID-a + Zure gaia + Trepetaren ID-a + Gelaren ID-a + + + Trepetak honako baliabide hauek erabili nahi ditu: + Baimendu + Blokeatu denak + Kamera erabili + Mikrofonoa erabili + DRM bidez babestutako multimedia irakurri + diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index fc9546d21d..ea8a6abc5a 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -257,11 +257,11 @@ Riot tarvitsee käyttöluvan kameraan ottaakseen kuvia ja suorittakseen videopuheluita. " \n -\nSalli kameran käyttö seuraavassa ponnahdusikkunassa soittaaksesi puhelun." +\nSoittaaksesi videopuhelun, salli seuraavassa ponnahdusikkunassa sovelluksen käyttää kameraa." Riot tarvitsee käyttöluvan mikrofoniin suorittakseen puheluita. " \n -\nSalli mikrofonin käyttö seuraavassa ponnahdusikkunassa soittaaksesi puhelun." +\nSoittaaksesi äänipuhelun, salli seuraavassa ponnahdusikkunassa sovelluksen käyttää mikrofonia." Riot tarvitsee käyttöluvan kameraan ja mikrofoniin suorittakseen videopuheluita. \n \nSalli mikrofonin ja kameran käyttö seuraavilla näytöillä aloittaaksesi tämän puhelun. @@ -1036,10 +1036,10 @@ Haluatko lisätä paketteja? \n%1$s Korjaa Play Services -palvelu - Firebase-token - FCM-token haettu onnistuneesti: + Firebase-tunniste + FCM-tunniste haettu onnistuneesti: \n%1$s - FCM-tokenin haku epäonnistui: + FCM-tunnisteen haku epäonnistui: \n%1$s [%1$s] \nTämä virhe ei ole Riotin hallinnassa ja Googlen mukaan tämä virhe tarkoittaa, että tällä laitteella on liikaa FCM:ään liittyneitä sovelluksia. Tämä virhe ilmenee vain tapauksissa, jossa on on erittäin paljon sovelluksia asennettuna, joten sen ei pitäisi vaikuttaa normaaliin käyttäjään. @@ -1049,9 +1049,9 @@ Haluatko lisätä paketteja? \nTämä virhe ei ole Riotin ratkaistavissa. Tässä puhelimessa ei ole Google-tiliä. Lisää laitteeseesi Google-tili tätä toimintoa varten. Lisää tili - Tokenin rekisteröinti - FCM-token rekisteröity onnistuneesti kotipalvelimelle. - FCM-tokenin rekisteröinti kotipalvelimelle epäonnistui: + Tunnisteen rekisteröinti + FCM-tunniste rekisteröity onnistuneesti kotipalvelimelle. + FCM-tunnisteen rekisteröinti kotipalvelimelle epäonnistui: \n%1$s Ilmoituspalvelu @@ -1479,7 +1479,7 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Tervetuloa kotiin! Löydät täältä lukemattomat viestit Keskustelut - Yksityiset keskustelusi näytetään tässä + Tässä näytetään yksityiset keskustelusi Huoneet Huoneesi näytetään tässä @@ -1601,8 +1601,8 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Huoneluettelo Julkaise tämä huone huoneluettelossa - Play Storen kuvaus - Push-säännöt + Play Storen kuvauksessa + Viesti-ilmoitusten säännöt %1$s luodaksesi tilin. Katkaise yhteys Kieltäydy @@ -1664,7 +1664,7 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Ota yksityiskohtaiset lokit käyttöön. Yritä uudelleen, kun olet hyväksynyt kotipalvelimesi käyttöehdot. - Palvelimen vastaus näyttää viipyvän. Tämä voi johtua kehnosta yhteydestä tai palvelimillamme tapahtuneesta virheestä. Yritä hetken kuluttua uudelleen. + Palvelimen vastaus näyttäisi olevan liian hidas. Tämä voi johtua kehnosta yhteydestä tai palvelimella olevasta ongelmasta. Yritä hetken kuluttua uudelleen. Lähetä liite @@ -1707,6 +1707,148 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Ole löydettävissä Tekstiviesti on lähetetty numeroon %s. Syötä sen sisältämä varmistuskoodi. - Push-sääntöjä ei ole määritetty + Viesti-ilmoitusten sääntöjä ei ole määritetty Luo uusi yksityiskeskustelu + Latn + + Kumoa + Tarkasta + Aseta puhelinnumerosi, ja voit myöhemmin antaa muiden löytää sinut puhelinnumerosi perusteella. + Aseta sähköpostisi tunnuksen palautusta varten. Myöhemmin voit asettaa sähköpostisi tai puhelinnumerosi löydettäviksi, jotta sinut voi löytää näillä tiedoilla. + Aseta sähköpostisi tunnuksen palautusta varten. Myöhemmin voit asettaa sähköpostisi tai puhelinnumerosi löydettäviksi, jotta sinut voi löytää näillä tiedoilla. + Vahvista salasanasi + Et voi tehdä tätä mobiili-Riotista + Tunnistautuminen vaaditaan + + + Integraatiot + Käytä integraatioiden lähdettä bottien, siltojen ja tarrapakettien hallintaan. +\nIntegraatioiden lähteet vastaanottavat asetusdataa ja voivat muokata sovelmia, lähettää kutsuja huoneeseen ja muokata oikeustasoja puolestasi. + Käyttäjien etsintä + Muokkaa etsinnän asetuksia. + Salli integraatiot + Integraatioiden lähde + + Sovelma + Lataa sovelma + Sovelman lisäsi: + Sovelman käyttö saattaa asettaa keksejä ja jakaa tietoa kohteen %s kanssa: + Sovelman käyttö saattaa jakaa tietoa kohteen %s kanssa: + Sovelman lataus epäonnistui. +\n%s + Lataa sovelma uudelleen + Avaa selaimessa + Kumoa minun pääsy + + Näyttönimesi + Profiilikuvasi osoite + Käyttäjätunnisteesi + Teemasi + Sovelman tunniste + Huoneen tunniste + + + Tämä sovelma haluaa käyttää seuraavia resursseja: + Salli + Estä kaikki + Käytä kameraa + Käytä mikrofonia + Lue DRM-suojattua mediaa + + Ei integraatioiden lähteitä asetettuna. + Huomiotta + + Olet kirjautunut ulos epäkelpojen tai vanhentuneiden pääsytietojen takia. + + Varmenna vertaamalla lyhyttä tekstijonoa. + Varmenna tämä laite merkkaamalla se luotetuksi. Kumppaneiden laitteisiin luottaminen antaa sinulle ylimääräistä mielenrauhaa, kun käytät osapuolten välistä salausta. + Tämän laitteen varmentaminen merkkaa sen luotetuksi, ja samoin sinun laitteesi merkataan luotetuksi kumppanisi näkökulmasta. + + Varmenna tämä laite varmistamalla, että seuraava emoji ilmestyy kumppanisi näytölle + Varmenna tämä laite varmistamalla, että seuraavat numerot ilmestyvät kumppanisi näytölle + + Turvalliset viestit tämän käyttäjän kanssa ovat salattuja päästä päähän, eivätkä kolmannet osapuolet voi lukea niitä. + Mitään ei tule näytölle\? Kaikki asiakasohjelmat eivät vielä tue interaktiivista varmennusta. Käytä vanhaa varmennustapaa. + Käytä vanhaa varmennustapaa. + + Laitteet eivät pysty sopimaan avaimista, tiivisteestä, MAC:sta tai SAS-metodista + Tiivisteet eivät täsmänneet + Vanhoissa Riotin versioissa oli tietoturvaongelma, joka saattoi antaa identiteettipalvelimelle(%1$s) pääsyn tunnukseesi. Jos luotat kohteeseen %2$s, voit jättää tämän huomiotta. Muussa tapauksessa, kirjaudu ulos ja kirjaudu uudelleen Riotiin. +\n +\nLue lisää: +\nhttps://medium.com/@RiotChat/36b4792ea0d6 + + Muuta + Viimeisin ominaisuuslista on aina %1$s. Jos löydät virheitä, lähetäthän virheraportin painikkeella, joka löytyy ylhäällä ja vasemmalla olevasta kotivalikosta, niin korjaamme vian niin nopeasti kuin pystymme. + Jos löytät virheitä, lähetäthän virheraportin ylhäältä ja vasemmalta löytyvästä kotivalikosta, niin korjaamme ongelman niin nopeasti kuin vain pystymme. + + Tuo osapuolten välisen salauksen avaimet tiedostosta ”%1$s”. + + Edistynyt + Ei rekisteröityjä viesti-ilmoitusten yhdyskäytäviä + + app_id: + push_key: + app_display_name: + device_name: + Formaatti: + + Rekisteröi tunniste + + RiotX on uusi Matrix-protokollaa (Matrix.org) käyttävä ohjelma: avointa ja hajautettua keskustelua tukeva turvallinen verkko. RiotX on täysin uusittu versio Riot Android -ohjelmasta, joka perustuu Matrix Android SDK:n uudelleenkirjoitukseen. +\n +\nHuomaa: tämä on betaversio. RiotX on vielä kehitystyössä, ja siinä on rajoitteita ja (toivottavasti ei kuitenkaan monta) bugeja. Kaikki palaute on tervetullutta! +\n +\nRiotX tukee: • Kirjaudu olemassaolevalle tunnukselle • Luo huoneita ja liity julkisiin huoneisiin • Hyväksy ja hylkää kutsuja • Listaa käyttäjän huoneet • Katso huoneen tietoja • Lähetä tekstiviestejä • Lähetä liitteitä • Lue ja kirjoita viestejä salatuissa huoneissa • Salaus: osapuolten välisen salauksen avaimien varmuuskopiointi, edistynyt laitteiden varmennus, avainten jakopyynnöt ja vastaus • Viesti-ilmoitukset • Vaalea, tumma ja musta teema +\n +\nKaikkia Riotin ominaisuuksia ei ole vielä toteutettu RiotX:ssä. Tärkeimmät puuttuvat (ja pian saapuvat!) ominaisuudet: • Tunnusten luonti • Huoneen asetukset (listaa huoneen jäsenet jne.) • Puhelut • Sovelmat • … + + Salataan pikkukuvaa… + Lähetetään pikkukuvaa (%1$s / %2$s) + Nimi tai tunniste (#example:matrix.org) + + Lisää Matrix-tunnisteella + Tuloksia ei löytynyt. Käytä ”Lisää Matrix-tunnisteella” etsiäksesi palvelimelta. + Suodata käyttäjätunnuksella tai tunnisteella… + + Etsittävät sähköpostiosoitteet + Vaihtoehdot ilmestyvät, kunhan olet lisännyt sähköpostiosoitteen. + Vaihtoehdot ilmestyvät, kunhan olet lisännyt puhelinnumeron. + Etsittävät puhelinnumerot + Runsassanaiset lokit auttavat antamalla enemmän tietoa kehittäjille, kun lähetät virheilmoituksen. Vaikka runsaammat lokit ovat käytössä, sovellus ei lähetä viestien sisältöjä tai mitään muuta yksityistä tietoa. + + + Avaa navigaatiovalikko + Avaa huoneen luontivalikko + Sulje huoneen luontivalikko… + Sulje avainten varmuuskopion mainos + Tiedosto ”%s$s” (%2$s) on liian iso lähetettäväksi. Raja on %3$s. + + Yhteystieto + Ääni + Jakotiedon käsittely epäonnistui + + Muokattu ilmianto + Ilmianna tämä sisältö + Sisällön ilmiannon syy + ILMIANNA + ESTÄ KÄYTTÄJÄ + + Sisältö ilmiannettu + Tämä sisältö on ilmiannettu. +\n +\nJos et halua nähdä enempää sisältöä tältä käyttäjältä, voit estää hänet piilottaaksesi hänen viestit + Ilmiannettu roskapostina + Tämä sisältö on ilmiannettu roskapostina. +\n +\nJos et halua nähdä enempää sisältöä tältä käyttäjältä, voit estää hänet piilottaaksesi hänen viestit + Ilmiannettu epäsopivana + Tämä sisältö on ilmiannettu epäsopivana. +\n +\nJos et halua nähdä enempää sisältöä tältä käyttäjältä, voit estää hänet piilottaaksesi hänen viestit + + Riot tarvitsee oikeuden tallentaakseen osapuolten välisen salauksen avaimesi talteen. +\n +\nSalli pääsy tiedostoihin seuraavassa ponnahdusikkunassa, jotta voit viedä avaimesi käsin. + diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 6057627163..cc81f595d2 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1709,7 +1709,7 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Réessayez quand vous aurez accepté les termes et conditions de votre serveur d’accueil. - On dirait que le serveur mette trop de temps à répondre. Ça peut être dû à une mauvaise connexion ou à une erreur avec nos serveurs. Réessayez plus tard. + On dirait que le serveur met trop de temps à répondre. Ça peut être dû à une mauvaise connexion ou à une erreur avec le serveur. Réessayez plus tard. Envoyer une pièce jointe @@ -1770,4 +1770,39 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Il n’y a aucune connexion au réseau pour le moment + Confirmez votre mot de passe + Vous ne pouvez pas faire cela depuis Riot mobile + Une authentification est nécessaire + + + Intégrations + Utilisez un gestionnaire d’intégrations pour gérer les bots, les passerelles, les widgets et les packs de stickers. +\nLes gestionnaires d’intégrations reçoivent des données de configuration et peuvent modifier des widgets, envoyer des invitations de salon et définir des rangs à votre place. + Autoriser les intégrations + Widget + Charger un widget + Ce widget a été ajouté par : + Son utilisation peut entraîner l’utilisation de cookies et le partage de données avec %s : + Son utilisation peut entraîner le partage de données avec %s : + Échec de chargement du widget. +\n%s + Recharger le widget + Ouvrir dans le navigateur + Révoquer l’accès pour moi + + Votre nom affiché + L’URL de votre avatar + Votre identifiant utilisateur + Votre thème + L’identifiant du widget + L’identifiant du salon + + + Ce widget veut utiliser les ressources suivantes : + Autoriser + Tout bloquer + Utiliser la caméra + Utiliser le micro + Lire des médias protégés par des DRM + diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index caca6bd7c8..1f518fd590 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1769,4 +1769,40 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Jelenleg nincs hálózati kapcsolat + Jelszó megerősítés + Riot mobilról ezt nem teheted meg + Azonosítás szükséges + + + Integrációk + Botok, hidak, kisalkalmazások és matrica csomagok kezeléséhez használj Integrációs Menedzsert. +\n +\nIntegrációs Menedzser megkapja a konfigurációt, módosíthat kisalkalmazásokat, szobához meghívót küldhet és a hozzáférési szintet beállíthatja helyetted. + Integrációk engedélyezése + Kisalkalmazás + Kisalkalmazás betöltése + Ezt a kisalkalmazást hozzáadta: + A használatához lehet, hogy sütiket kell használni és adat lesz megosztva ezzel: %s: + A használatához lehet, hogy adat lesz megosztva ezzel: %s: + Kisalkalmazás betöltése sikertelen: +\n%s + Kisalkalmazás újratöltése + Megnyitás böngészőben + Hozzáférés megvonása magamtól + + Megjelenítési neved + Profilképed URL-je + Felhasználói azonosítód + Témád + Kisalkalmazás azon. + Szoba azonosító + + + Ez a kisalkalmazás az alábbi erőforrásokat szeretné használni: + Engedélyez + Mind tiltása + Kamera használata + Mikrofon használata + DRM-mel védett média olvasása + diff --git a/vector/src/main/res/values-id/strings.xml b/vector/src/main/res/values-id/strings.xml index 56f6d4deb2..692fa7ad13 100644 --- a/vector/src/main/res/values-id/strings.xml +++ b/vector/src/main/res/values-id/strings.xml @@ -1,6 +1,6 @@ - in + id ID Pesan diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index d46eaa987b..ddc19fe125 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -10,39 +10,39 @@ Messaggi Stanza Impostazioni - Dettagli membri + Dettagli sui membri Cronologia OK Annulla Salva - Lascia + Esci Invia Copia - Rinvia + Rispedisci Rimuovi - Cita + Citazione Condividi Ultimi Inoltra - Collegamento permanente - Vedi sorgente - Vedi sorgente decriptato + Permalink + Vedi il codice sorgente + Vedi il codice sorgente decifrato Elimina Rinomina - Segnala contenuto - Chiamata attiva - Chiamata di conferenza in corso. + Segnala questo contenuto + Chiamata in corso + Avvio conferenza. \nUnisciti come %1$s o %2$s - Voce + Audio Video Impossibile avviare la chiamata, riprova più tardi - A causa di permessi insufficienti, potrebbero mancare alcune funzioni… - Necessiti del permesso per invitare ad iniziare una conferenza in questa stanza + Poichè i tuoi permessi non sono sufficienti, alcune funzioni potrebbero non esser disponibili… + Non hai permessi sufficienti per avviare una conferenza in questa stanza Impossibile avviare la chiamata - Informazioni dispositivo - Le chiamate in modalità conferenza non sono supportate nelle stanze criptate + Informazioni sul dispositivo + Le stanze criptate non supportano le conferenze Invia comunque o Invita @@ -50,7 +50,7 @@ Disconnetti - Chiamata voce + Chiamata audio Chiamata video Ricerca globale Segna tutti come letti @@ -68,14 +68,14 @@ Home Preferiti - Persone + Chat dirette Stanze - Filtra i nomi delle stanze - Filtra i preferiti - Filtra le persone - Filtra i nomi delle stanze + Cerca + Cerca tra i preferiti + Cerca tra le chat dirette + Cerca tra le stanze Inviti @@ -83,11 +83,11 @@ Conversazioni - Libro degli indirizzi locale + Rubrica locale Elenco utenti - Solo contatti Matrix + Mostra solo i contatti Matrix Nessuna conversazione - Riot non ha il permesso di accedere ai tuoi contatti locali + Riot non ha avuto l\'autorizzazione ad accedere alla tua Rubrica locale Nessun risultato @@ -100,18 +100,18 @@ %d utenti - Invia registri - Invia registri di crash - Invia istantanea + Invia i registri + Invia i registri di crash + Invia schermata Segnala errore - Per favore descrivi l\'errore. Cosa hai fatto? Cosa ti aspettavi dovesse accadere? Cosa è effettivamente successo? + Per favore descrivi l\'errore. Cosa stavi facendo\? Cosa ti aspettavi dovesse accadere\? Cosa è effettivamente successo\? Descrivi qui il problema - Al fine di diagnosticare i problemi, i registri Riot di questo dispositivo saranno inviati con il rapporto dell\'errore. Questo rapporto, che include i registri e l\'istantanea grafica, non sarà pubblicamente visibile. Se preferisci inviare solo il testo soprastante, deseleziona: - Sembra che tu stia scuotendo il dispositivo con frustrazione. Desideri aprire la schermata di segnalazione errore? - L\'applicazione è andata in crash l\'ultima volta. Desideri aprire la schermata di segnalazione del crash? + Per permettere una diagnosi del problema, oltre alla segnalazione errore verranno inviati anche i registri di Riot. Rapporto, registri e schermata non saranno resi pubblici. Tuttavia, se preferisci inviare solamente la segnalazione, deseleziona: + Sembra tu stia scuotendo il dispositivo con rabbia. Vuoi segnalare un errore\? + L\'ultima volta l\'applicazione è andata in crash. Vuoi inviare una segnalazione\? - La segnalazione dell\'errore è stata spedita con successo - Fallito l\'invio della segnalazione di errore (%s) + La segnalazione errore è stata spedita + L\'invio della segnalazione errore è fallito (%s) Avanzamento (%s%%) Invia file in @@ -122,16 +122,16 @@ Crea account Accedi Disconnetti - URL server home - URL server identità + URL dell\'Home Server + URL dell\'Identity Server Cerca - Inizia nuova chat - Avvia chiamata voce + Avvia una nuova chat diretta + Avvia chiamata audio Avvia chiamata video Invia file - Cattura foto o video + Fai una foto o un video Accedi @@ -152,35 +152,33 @@ Conferma la nuova password Nome utente e/o password non corretti Il nome utente può contenere solo lettere, numeri, punti, trattini e linee basse - Password troppo breve (min 6) - Password mancante - Non sembra essere un indirizzo email valido - Non sembra essere un numero di telefono valido - L\'indirizzo email è già stato definito. - Indirizzo email mancante - Numero di telefono mancante - Indirizzo email o numero di telefono mancante + Password troppo corta (min 6) + Manca la password + Questo indirizzo email non sembra corretto + Questo numero di telefono non sembra corretto + L\'indirizzo email è già stato impostato. + Manca l\'indirizzo email + Manca il numero di telefono + Manca l\'indirizzo email o il numero di telefono Token non valido Le password non corrispondono - Password dimenticata? + Hai dimenticato la password\? Usa opzioni server personalizzate (avanzate) Per favore controlla la tua email per proseguire la registrazione - La registrazione con email e numero di telefono in una volta sola non è ancora supportata (relative API in sviluppo). Sarà utilizzato solo il numero di telefono. - -Puoi aggiungere la tua email al tuo profilo dalle impostazioni. - Questo server home vorrebbe assicurarsi che tu non sia un robot - Nome utente in uso - Server home: - Server identità: + La registrazione con email e numero di telefono in una volta sola non è ancora supportata. Sarà utilizzato solo il numero di telefono. Potrai aggiungere l\'indirizzo email al tuo profilo dalle impostazioni. + Questo Home Server vorrebbe assicurarsi che tu non sia un robot + Nome utente già in uso + Home Server: + Identity Server: Ho verificato il mio indirizzo email - Per ripristinare la password, inserisci l\'indirizzo email associato al tuo account: - L\'indirizzo email associato al tuo account deve essere inserito. - Una nuova password deve essere inserita. - Un\'email è stata inviata a %s. Appena avrai seguito il link lì contenuto, clicca qui sotto. - Verifica indirizzo email fallita: assicurati di aver cliccato sul link contenuto nell\'email - La tua password è stata ripristinata. - -Sei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per riabilitare le notifiche, riconnettiti su ciascun dispositivo. + Per reimpostare la password, inserisci l\'indirizzo email associato al tuo account: + Va inserito l\'indirizzo email associato al tuo account. + Va inserita una nuova password. + E\' stata inviata una mail a %s. Dopo che avrai seguito il link che contiene, clicca qui sotto. + La verifica del tuo indirizzo email è fallita: assicurati di aver cliccato sul link contenuto nella mail + La tua password è stata reimpostata. +\n +\nSei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notifica. Per ripristinare le notifiche, riconnettiti su ciascun dispositivo. L\'URL deve iniziare con http[s]:// @@ -191,18 +189,18 @@ Sei stato disconnesso da tutti i dispositivi e non riceverai più alcuna notific Impossibile registrarsi: difetto proprietà dell\'email Inserisci un URL valido - Nome utente/password non validi - Il token di accesso specificato non è stato riconosciuto + Nome utente/password non corretto + Il Token di accesso specificato non è stato riconosciuto JSON malformato Non conteneva un JSON valido Sono state inviate troppe richieste Questo nome utente è già in uso - Il link nell\'email non è ancora stato cliccato + Il link nella mail non è ancora stato cliccato - È necessario eseguire nuovamente l\'accesso per generare le chiavi di crittografia da-utente-a-utente per questo dispositivo ed inviare la chiave pubblica al server home. -Ciò può capitare solo una volta. -Ci scusiamo per l\'inconveniente. + È necessario eseguire nuovamente l\'accesso per generare le chiavi crittografiche per questo dispositivo ed inviare la chiave pubblica al\'Home Server. +\nQuesto inconveniente non capiterà più. +\nScusa per il disguido. Elenco ricevute lette @@ -231,46 +229,46 @@ Ci scusiamo per l\'inconveniente. Argomento stanza - Chiamata connessa + Chiamata in corso Chiamata in connessione… Chiamata terminata - Sto chiamando… + Chiamata in corso… Chiamata in arrivo - Videochiamata in arrivo - Chiamata voce in arrivo + Chiamata video in arrivo + Chiamata audio in arrivo Chiamata in corso… Ricezione fallita da parte del destinatario. - Fallita connessione al supporto multimediale + La connessione al supporto multimediale è fallita Impossibile avviare la fotocamera chiamata risposta altrove - Cattura foto o video + Fai una foto o un video Impossibile registrare video Informazione - Riot necessita del permesso di accedere alla tua galleria di foto e video per inviare e salvare allegati. - -Per favore consenti l\'accesso nella prossima finestra così da poter inviare file dal tuo telefono. - Riot necessita del permesso di accedere alla fotocamera per scattare foto e fare videochiamate. - - -Per favore dai l\'autorizzazione nella prossima finestra al fine di effettuare la chiamata. - Riot necessita del permesso di accedere al microfono per eseguire chiamate audio. - - -Per favore dai l\'autorizzazione nella prossima finestra al fine di effettuare la chiamata. - Riot necessita del permesso di accedere alla fotocamera ed al microfono per eseguire videochiamate. - -Per favore consenti l\'accesso nella prossima finestra per potere effettuare la chiamata. - Riot può accedere alla tua rubrica per trovare altri utenti Matrix basandosi sulla loro email e sul numero di telefono. Se sei d\'accordo sulla condivisione della tua rubrica a questo scopo, consenti l\'accesso nella prossima finestra. - Riot può accedere alla tua rubrica per trovare altri utenti Matrix basandosi sulla loro email e sul numero di telefono. + Riot deve essere autorizzato ad accedere alla tua Galleria di foto e video per poter inviare e salvare allegati. \n -\nSei d\'accordo sulla condivisione della tua rubrica a questo scopo\? +\nNel prossimo pop-up concedi l\'autorizzazione per poteri inviare file dal tuo dispositivo. + Riot deve essere autorizzato ad accedere alla tua fotocamera per poter fare foto e video. + " +\n +\nNel prossimo pop-up concedi l\'autorizzazione per poter fare la chiamata." + Riot deve essere autorizzato ad accedere al microfono e poter così fare chiamate audio. + " +\n +\nNel prossimo pop-up concedi l\'autorizzazione per poter fare la chiamata." + Riot deve essere autorizzato ad accedere a fotocamera e microfono per poter fare chiamate video. +\n +\nNel prossimo pop-up concedi le autorizzazioni per poter fare la chiamata. + Riot può usare tua Rubrica locale per trovare altri utenti Matrix grazie alle loro email e numeri di telefono. Se ti sta bene comunicare i dati di tutti i tuoi contatti all\'Identity Server, puoi concedere l\'autorizzazione nel prossimo pop-up. + Riot può usare tua Rubrica locale per trovare altri utenti Matrix grazie alle loro email e numeri di telefono. +\n +\nTi sta bene comunicare i dati di tutti i tuoi contatti per questo scopo\? - Spiacente. Azione non eseguita a causa di mancate autorizzazioni + Purtroppo l\'azione non è stata eseguita poichè mancano i permessi Salvato @@ -280,7 +278,7 @@ Per favore consenti l\'accesso nella prossima finestra per potere effettuare la Continua - Rimuoviti + Rimuovi Unisciti Anteprima Rifiuta @@ -289,19 +287,20 @@ Per favore consenti l\'accesso nella prossima finestra per potere effettuare la Vai al primo messaggio non letto. - Sei stato invitato ad unirti in questa stanza da %s - Questo invito è stato spedito da %s, che non è associato a questo account. È possibile che tu voglia connetterti con un altro account, o aggiungere l\'email al tuo account. + Sei stato invitato ad entrare in questa stanza da %s + Questo invito è stato spedito a %s, che non è associato a questo account. +\nPuoi aggiungere questa email al tuo account o provare ad accedere con un account differente. Stai provando ad accedere a %s. Desideri entrare per partecipare alla discussione? una stanza - Questa è l\'anteprima della stanza. Le interazioni con la stanza sono state disabilitate. + Questa è l\'anteprima della stanza. Le interazioni sono disabilitate. Nuova chat - Aggiungi membro - 1 membro + Aggiungi utente + 1 utente - Lascia stanza + Esci dalla stanza Sei sicuro di voler uscire dalla stanza? Sei sicuro di voler rimuovere %s da questa chat? Crea @@ -316,32 +315,32 @@ Per favore consenti l\'accesso nella prossima finestra per potere effettuare la DISPOSITIVI Invita - Lascia questa stanza - Rimuovi da questa stanza - Bandisci - Togli il bando + Esci da questa stanza + Butta fuori da questa stanza + Bannalo + Togli il ban Ripristina ad utente normale - Rendi moderatore - Rendi amministratore + Nomina moderatore + Nomina amministratore Nascondi tutti i messaggi di questo utente Mostra tutti i messaggi di questo utente ID utente, nome o email - Citazione - Mostra elenco dispositivi - Non potrai annullare questa modifica in quanto promuoverai l\'utente al tuo stesso livello di potere. -Sicuro di procedere? + Cita + Mostra l\'elenco dei dispositivi + La nomina non potrà essere annullata perché darai a questo utente i tuoi stessi poteri. +\nSicuro di voler procedere\? - "Sicuro di voler invitare %s in questa chat?" + Sicuro di voler invitare %s\? - Invita tramite ID + Invita tramite ID utente CONTATTI LOCALI (%d) ELENCO UTENTI (%s) Solo utenti Matrix - Invita utente tramite ID - Per favore inserisci uno o più indirizzi email o ID Matrix - Email o ID Matrix + Invita tramite ID utente + Per favore inserisci uno o più indirizzi email o ID utente + Email o ID utente Cerca @@ -352,11 +351,11 @@ Sicuro di procedere? Messaggio non criptato… La connessione al server è stata persa. Messaggi non inviati. %1$s o %2$s ora? - Messaggi non inviati a causa di dispositivi sconosciuti qui presenti. %1$s o %2$s ora? + Messaggi non inviati a causa della presenza di dispositivi sconosciuti. %1$s o %2$s ora\? Rispedisci tutto Annulla tutto - Rinvia messaggi non spediti - Elimina messaggi non spediti + Rispedisci messaggi non inviati + Elimina i messaggi non inviati File non trovato Non hai il permesso di pubblicare in questa stanza @@ -367,40 +366,40 @@ Sicuro di procedere? Ignora Impronta digitale (%s): Impossibile verificare l\'identità del server remoto. - Ciò potrebbe significare che qualcuno sta intercettando il tuo traffico, oppure il tuo telefono non riconosce come affidabile il certificato fornito dal server remoto. - Se l\'amministratore del server ha dichiarato che questa anomalia è prevista, assicurati che l\'impronta digitale corrisponda a quella fornita da loro. - Il certificato è cambiato rispetto a quello ritenuto affidabile dal tuo telefono. Questo è ESTREMAMENTE INSOLITO. Si raccomanda di NON ACCETTARE questo nuovo certificato. - Il certificato è cambiato da quello precedente ritenuto affidabile ad uno non affidabile. Il server potrebbe aver rinnovato il proprio certificato. Contatta l\'amministratore del server per chiedere l\'impronta digitale prevista. - Accetta il certificato solo se l\'amministratore ha pubblicato un\'impronta digitale che corrisponda a quella qua sopra. + Ciò potrebbe voler dire che qualcuno sta intercettando il tuo traffico dati, oppure che il tuo telefono non riconosce come affidabile il certificato fornito dal server remoto. + Se l\'amministratore del server ha dichiarato che questa anomalia è prevista, assicurati che l\'impronta digitale corrisponda a quella fornita dal server stesso.. + Il certificato è diverso da quello precedentemente contrassegnato sul tuo telefono come \"affidabile\". Questa cosa è MOLTO INSOLITA. Si raccomanda di NON ACCETTARE questo nuovo certificato. + Il certificato del server è cambiato: quello precedente era stato contrassegnato come affidabile ma quello attuale no. Può darsi che il certificato precedente sia scaduto e sia stato semplicemente sostituito con uno nuovo. Contatta l\'amministratore del server per verificare l\'impronta digitale in uso. + Contrassegna il certificato come affidabile solo se l\'mpronta digitale comunicata dall\'amministratore del server corrisponde a quella qua sopra. Dettagli stanza - Persone + Utenti File Impostazioni - ID malformato. Dovrebbe essere un indirizzo email o un ID Matrix come \'@localpart:domain\' + ID malformato. Dovrebbe essere un indirizzo email o un ID utente come \'@localpart:domain\' INVITATI - AGGIUNTI + MEMBRI - Motivo per segnalare questo contenuto - Desideri nascondere tutti i messaggi di questo utente? - -Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tempo. - Annulla caricamento - Annulla scaricamento + Motivo per cui segnali questo contenuto + Desideri nascondere tutti i messaggi di questo utente\? +\n +\nTieni presente che questa azione riavvierà l\'app e ciò potrebbe richiedere molto tempo. + Annulla l\'upload + Annulla il download Cerca - Filtra membri della stanza + Cerca tra i membri della stanza Nessun risultato STANZE MESSAGGI - PERSONE + UTENTI FILE - ENTRARE + ENTRA ELENCO PREFERITI STANZE @@ -410,17 +409,17 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Crea stanza Entra nella stanza Entra in una stanza - Digita ID o soprannome (alias) + Digita l\'ID stanza o il suo nome - Esplora elenco + Esplora l\'elenco Ricerca negli elenchi… Preferito Bassa priorità Chat diretta - Lascia conversazione + Esci dalla conversazione Dimentica @@ -434,30 +433,30 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem - Immagine profilo + Immagine del profilo Nome visualizzato Email Aggiungi indirizzo email Telefono Aggiungi numero di telefono - Mostra le informazioni dell\'app nelle impostazioni di sistema. - Informazioni applicazione + Mostra le informazioni dell\'App nelle Impostazioni di sistema. + Informazioni sull\'App - Abilita notifiche per questo account - Abilita notifiche per questo dispositivo + Abilita le notifiche per questo account + Abilita le notifiche per questo dispositivo Accendi lo schermo per 3 secondi alla notifica - Msg nelle chat uno-a-uno - Msg nelle chat di gruppo - Quando vengo invitato in una stanza + Messaggi nelle chat dirette + Messaggi nelle chat di gruppo + Invito ad entrare in una stanza Invito ad una chiamata Messaggi inviati da bot Esegui all\'avvio - Sincronizzazione in sottofondo - Abilita sync in sottofondo - Attesa per una richiesta di sync - Attesa per ogni sincronizzazione + Sincronizzazione in background + Abilita la sincronizzazione in background + La richiesta di sincronizzazione sta impiegando troppo tempo + Ritardo tra ogni sincronizzazione secondo secondi @@ -467,9 +466,9 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Avvisi di terze parti Copyright Politica sulla privacy - Elimina cache - Elimina file multimediali temporanei - Conserva file multimediali + Svuota la cache + Svuota la cache dei file multimediali + Conserva i file multimediali Impostazioni utente Notifiche @@ -479,63 +478,63 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Crittografia Target delle notifiche Contatti locali - Autorizzazione contatti - Nazione rubrica telefonica + Consenti l\'accesso alla Rubrica locale + Prefisso telefonico internazionale Schermata iniziale Segna le stanze con notifiche perse Segna le stanze con messaggi non letti Dispositivi Mostra data e ora di tutti i messaggi - Modalità risparmio banda + Modalità risparmio dati - Informazioni dispositivo + Informazioni sul dispositivo ID Nome pubblico - Aggiorna nome pubblico - Ultima visualizzazione + Aggiorna il nome pubblico + Ultimo accesso %1$s @ %2$s Questa operazione richiede un\'autenticazione aggiuntiva.\nPer proseguire, inserisci la tua password. Autenticazione Password: Invia - Connesso come - Server home - Server identità + Autenticato come + Home Server + Identity Server Interfaccia utente Lingua Scegli una lingua In attesa di verifica - Controlla la tua email e clicca sul link lì contenuto. Fatto questo, clicca su Continua. - Impossibile verificare l\'indirizzo email. Controlla la tua email e clicca sul link lì contenuto. Fatto questo, clicca su continua. + Controlla la tua email e clicca sul link che ti è stato spedito. Fatto questo, clicca su Continua. + Impossibile verificare l\'indirizzo email. Controlla la tua email e clicca sul link che ti è stato spedito. Fatto questo, clicca su Continua. Questo indirizzo email è già in uso. Questo indirizzo email non è stato trovato. Questo numero di telefono è già in uso. - Cambia password + Cambia la password Password attuale Nuova password - Conferma nuova password - Fallito l\'aggiornamento password + Conferma la nuova password + Fallito l\'aggiornamento della password La tua password è stata aggiornata - Mostrare tutti i messaggi di %s? - -Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tempo. + Mostrare tutti i messaggi di %s\? +\n +\nTieni presente che questa azione riavvierà l\'app e ciò potrebbe richiedere molto tempo. Sicuro di voler rimuovere questo target di notifica? Sicuro di voler rimuovere %1$s %2$s? - Scegli una nazione + Scegli un paese - Nazione - Per favore scegli una nazione + Paese + Per favore scegli un paese Numero di telefono - Numero di telefono non valido per la nazione selezionata + Numero di telefono non valido per il paese selezionato Verifica numero di telefono - "Un SMS è stato spedito con il codice di attivazione. Per favore inserisci il codice qui sotto." + E\' stato spedito un SMS con il codice di attivazione. Per favore inserisci il codice qui sotto. Inserisci un codice di attivazione Errore durante la convalida del numero di telefono Codice @@ -549,23 +548,23 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem - Immagine stanza - Nome stanza + Icona della stanza + Nome della stanza Argomento - Etichetta stanza - Etichettata come: + Etichetta + Etichetta come: Preferito Bassa priorità - Niente + Nessuna etichetta Accesso e visibilità - Mostra questa stanza nell\'elenco delle stanze + Mostra questa stanza nell\'elenco delle stanze pubbliche Accesso alla stanza - Leggibilità cronologia della stanza - Chi può leggere la cronologia? + Accesso alla Timeline + Chi può leggere la Timeline\? Chi può entrare in questa stanza? @@ -575,37 +574,37 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Solo i membri (dal momento in cui entrano nella stanza) - Per indirizzare ad una stanza tramite link, essa deve avere un indirizzo. + Una stanza deve avere un indirizzo per poter essere linkata. Solo le persone che sono state invitate Chiunque conosca il link della stanza, eccetto gli ospiti Chiunque conosca il link della stanza, compresi gli ospiti - Utenti banditi + Utenti bannati Avanzate ID interno della stanza Indirizzi Laboratorio - Queste sono caratteristiche sperimentali che possono fallire in modo inatteso. Usare con cautela. + Queste sono caratteristiche sperimentali che potrebbero dare risultati inattesi. Usare con cautela. Crittografia da-utente-a-utente La crittografia da-utente-a-utente è attiva Devi disconnetterti per abilitare la crittografia. Cripta solo per i dispositivi verificati - Non inviare mai da questa stanza messaggi criptati a dispositivi non verificati. + Non inviare mai da questa stanza dei messaggi criptati verso dispositivi che non siano stati verificati da questo stesso dispositivo. Questa stanza non ha indirizzi locali - Nuovo indirizzo (es. #foo:matrix.org) + Nuovo indirizzo stanza (es. #foo:matrix.org) - Formato soprannome non valido - \'%s\' non è un formato valido per un soprannome - Non avrai un indirizzo principale specificato per questa stanza. + Il formato del nome della stanza non è corretto + \'%s\' non è un formato valido per un nome di stanza + Non avrai un indirizzo principale specifico per questa stanza. Avvisi per l\'indirizzo principale Imposta come indirizzo principale - Non più come indirizzo principale + Non usare più come indirizzo principale Copia ID stanza Copia indirizzo stanza @@ -618,20 +617,20 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Tema - %s stava cercando di caricare un punto specifico della cronologia di questa stanza, ma non l\'ha trovato. + %s stava cercando di caricare un punto specifico tra i messaggi passati di questa stanza, ma non l\'ha trovato. - Informazioni crittografia da-utente-a-utente + Informazioni sulla crittografia E2E - Informazioni evento + Informazioni sull\'evento ID utente Chiave identità Curve25519 Richiesta chiave per l\'impronta digitale Ed25519 Algoritmo ID sessione - Errore decriptazione + Errore di decriptazione - Informazioni dispositivo mittente + Informazioni sul dispositivo mittente Nome pubblico Nome pubblico ID @@ -639,56 +638,62 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Verifica Impronta digitale Ed25519 - Esporta chiavi da-utente-a-utente delle stanze + Esporta le chiavi di crittografia delle stanze Esporta le chiavi delle stanze Esporta le chiavi in un file locale Esporta - Inserisci frase d\'accesso (passphrase) - Conferma frase d\'accesso (passphrase) - Le chiavi da-utente-a-utente della stanza sono state salvate su \'%s\'. + Inserisci password + Conferma password + Le chiavi di crittografia della stanza sono state salvate su \'%s\'. +\n +\nAttenzione: se si disinstalla quest\'applicazione il file, viene eliminato. -Attenzione: questo file può essere eliminato se l\'applicazione viene disinstallata. - - Importa chiavi da-utente-a-utente delle stanze - Importa chiavi delle stanze + Importa le chiavi di crittografia della stanza + Importa le chiavi della stanza Importa le chiavi da un file locale Importa Cripta solo per i dispositivi verificati - Non inviare mai da questo dispositivo messaggi criptati a dispositivi non verificati. + Non inviare mai da questo dispositivo messaggi criptati verso dispositivi non verificati. NON verificato Verificato - Nella lista nera + Metti in lista nera dispositivo sconosciuto niente Conferma - Sconferma - Nella lista nera - Non nella lista nera + Rimuovi la conferma + Lista nera + Togli dalla lista nera - Verifica dispositivo - Per verificare che quel dispositivo è affidabile, contatta il suo proprietario utilizzando altri canali di comunicazione (ad esempio, per persona o per telefono) e chiedigli se nelle Impostazioni, sezione Crittografia, la voce Chiave dispositivo corrisponde al codice riportato qui sotto: - Se ciò corrisponde, premi il pulsante di verifica qui sotto. In caso contrario, qualcun altro sta intercettando questo dispositivo e probabilmente dovresti inserirlo nella lista nera. In futuro il processo di verifica sarà più sofisticato. - Ho verificato che le chiavi corrispondano + Verifica il dispositivo + Per verificare se quel dispositivo è affidabile, contatta il suo proprietario utilizzando altri canali di comunicazione (ad esempio, per persona o per telefono) e chiedigli se nelle Impostazioni, sezione Crittografia, la chiave del dispositivo corrisponde a quella riportata qui sotto: + Se corrisponde, premi il pulsante di verifica qui sotto. In caso contrario, qualcun altro sta intercettando questo dispositivo e probabilmente dovresti metterlo in lista nera. In futuro il processo di verifica sarà più sofisticato. + Ho verificato che le chiavi corrispondono - Riot supporta ora la crittografia da-utente-a-utente, ma devi riconnetterti per abilitarla.\n\nPuoi farlo ora o più tardi dalle impostazioni dell\'applicazione. + Riot supporta ora la crittografia da-utente-a-utente, ma per abilitarla devi riconnetterti. +\n +\nPuoi farlo ora, o più tardi, dalle impostazioni dell\'applicazione. La stanza contiene dispositivi sconosciuti - Questa stanza contiene dispositivi sconosciuti che non sono stati verificati.\nCiò significa che non esiste alcuna garanzia che i dispositivi appartengano agli utenti che ne rivendicano il possesso.\nSi consiglia di passare attraverso il processo di verifica per ogni dispositivo prima di continuare, ma è possibile inviare ugualmente il messaggio senza fare la verifica se preferisci.\n\nDispositivi sconosciuti: + Questa stanza contiene dispositivi sconosciuti che non sono stati verificati. +\nNon v\'è alcuna garanzia che le persone che utilizzano quei dispositivi siano davvero chi dicono d\'essere. +\nSi consiglia di verificare ogni dispositivo prima di continuare, ma se si preferisce, è comunque possibile inviare ugualmente il messaggio anche senza la verifica. +\n +\nDispositivi sconosciuti: Scegli un elenco di stanze Il server potrebbe essere non disponibile o sovraccarico - Digita un server home per elencare le sue stanze pubbliche - URL server home - Tutte le stanze sul server home %s + Inserisci un Home Server per vedere le sue stanze pubbliche + URL dell\'Home Server + Tutte le stanze sull\'Home Server %s Tutte le stanze native %s - Cerca nella cronologia + Cerca tra i messaggi passati Grandezza font @@ -704,40 +709,40 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal Tema Nero Sincronizzazione… - Suono notifiche + Suono delle notifiche Mostra gli orari in formato 12 ore - Hai bisogno dei permessi per gestire i widget in questa stanza + Devi avere il permesso per poter gestire i widget in questa stanza %1$s aggiunto da %2$s %1$s rimosso da %2$s - Crea una videoconferenza con jitsi - Sei sicuro di voler eliminare questo widget da questa stanza? + Avvia una conferenza usando Jitsi + Vuoi davvero eliminare questo widget dalla stanza\? Impossibile creare il widget. - Invio della richiesta fallito. + L\'invio della richiesta è fallito. Non sei in questa stanza. Non hai i permessi per eseguire l\'azione in questa stanza. - room_id mancante nella richiesta. - user_id mancante nella richiesta. - Aggiungi una app Matrix - Cattura foto - Cattura video + Nella richiesta manca l\'ID stanza. + Nella richiesta manca l\'ID utente. + Aggiungi un Widget + Fai una foto + Fai un video - Msg contenenti il nome mostrato - Msg contenenti il nome utente + Messaggi contenenti il nome mostrato + Messaggi contenenti il nome utente Notifiche silenziose Notifiche audio - In ascolto di eventi - Rapporto errori + Rilevazione eventi + Segnalazione errore - Cellulare + Smartphone Chiama Messaggio criptato - Dettagli della comunità + Dettagli sulla comunità Caricamento… @@ -745,32 +750,32 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal Azioni Comunità - Filtra i nomi delle comunità + Cerca tra le e comunità Invita Comunità Nessun gruppo - Agita con rabbia per segnalare un errore + Per segnalare un errore agita il dispositivo con rabbia - Sicuro di voler iniziare una nuova conversazione con %s? - Sicuro di voler iniziare una chiamata vocale? - Sicuro di voler iniziare una chiamata video? + Sicuro di voler avviare una nuova chat con %s\? + Sicuro di voler fare una chiamata audio\? + Sicuro di voler fare una chiamata video\? Elenco gruppi - Elenca i membri - Apri intestazione + Elenco dei membri + Apri descrizione Sincronizzazione… - 1 membro attivo - %d membri attivi + 1 utente attivo + %d utenti attivi - 1 membro - %d membri + 1 utente + %d utenti - Sei sicuro di voler bandire dalla chat questo utente? + Sei sicuro di voler bannare questo utente\? 1 nuovo messaggio @@ -789,16 +794,16 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal Tutti i messaggi Solo le citazioni Silenzioso - Aggiungi scorciatoia su schermata iniziale + Aggiungi scorciatoia sulla schermata iniziale - Anteprima degli URL in linea + Anteprima degli URL Vibra quando menzionano un utente Statistiche Notifiche Nuovo ID della comunità (es. +foo:matrix.org) - ID comunità non valido + L\'ID comunità non è valido \'%s\' non è un ID comunità valido @@ -816,7 +821,7 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal %1$s in %2$s - Creazione del widget fallita + La creazione del widget è fallita 1 widget attivo %d widget attivi @@ -824,18 +829,18 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal La stanza %s non è visibile. - Usa fotocamera nativa + Usa la fotocamera di sistema Hai aggiunto un nuovo dispositivo \'%s\' che sta richiedendo le chiavi crittografiche. - Il tuo dispositivo non verificato \'%s\' sta richiedendo le chiavi crittografiche. - Inizia la verifica + Il tuo dispositivo non verificato \'%s\' sta chiedendo le chiavi crittografiche. + Avvia la verifica Condividi senza verificare Ignora la richiesta Attenzione! - La chiamata in conferenza è in fase di sviluppo e potrebbe non essere affidabile. + Le conferenze sono in fase di sviluppo e potrebbero non essere affidabili. Errore di comando @@ -849,7 +854,7 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal Crea una comunità Nome della comunità Esempio - ID della comunità + ID comunità esempio @@ -860,25 +865,25 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal Stanze Si è unito Invitato - Filtra i membri del gruppo - Filtra le stanze del gruppo + Cerca tra i membri del gruppo + Cerca tra le stanze del gruppo - L\'amministratore non ha fornito una descrizione estesa per questa comunità. + L\'amministratore non ha fornito una descrizione estesa di questa comunità. %2$s ti ha buttato fuori da %1$s - %2$s ti ha bandito da %1$s + %2$s ti ha bannato da %1$s Motivo: %1$s Rientra Dimentica la stanza Avatar - Insegna + Predisposizione - Questa stanza non mostra insegne per alcuna comunità + Questa stanza non mostra predisposizione per alcuna comunità Il livello di potere deve essere un intero positivo. - Notificare avatar - Avatar ricevuto + Avatar di avviso + Avatar di ricezione 1 cambio d\'appartenenza %d cambi d\'appartenenza @@ -887,63 +892,63 @@ Attenzione: questo file può essere eliminato se l\'applicazione viene disinstal Privacy delle notifiche Normale Privacy ridotta - L\'app richiede il permesso per funzionare in sottofondo + L\'App deve essere autorizzata a funzionare in background • Le notifiche vengono inviate via Firebase Cloud Messaging • Le notifiche contengono solo metadati - • Il contenuto del messaggio di una notifica si trova al sicuro direttamente dal server home di Matrix - • Le notifiche contengono dati e metadati sul messaggio + • Il contenuto del messaggio di una notifica è emesso direttamente dall\'Home Server Matrix + • Le notifiche contengono metadati e contenuto del messaggio • Le notifiche non mostreranno il contenuto del messaggio - Privacy notifiche - Riot può funzionare in sottofondo per gestire le tue notifiche in modo sicuro e privato. Ciò può influenzare la durata della batteria. + Privacy delle notifiche + Riot può esser sempre attivo in background in modo da gestire le tue notifiche in modo costante e sicuro. Ciò può influire sulla durata della batteria. Concedi l\'autorizzazione Scegli un\'altra opzione - Invia un adesivo + Invia uno sticker - Invia adesivo - Non hai ancora alcun pacco di adesivi attivo. -\n -\nAggiungerne qualcuno ora\? + Invia sticker + Non ci sono pacchetti di sticker attivi. +\n +\nVuoi aggiungerne qualcuno\? - Disattiva iscrizione + Disattiva l\'account Disattiva il mio account - Invia statistiche di utilizzo - Riot raccoglie statistiche anonime per permetterci di migliorare l\'applicazione. - Attiva le statistiche per aiutarci a migliorare Riot. + Invia le statistiche di utilizzo + Riot raccoglie statistiche anonime per permettere il miglioramento dell\'applicazione. + Attiva le statistiche per aiutare a migliorare Riot. Sì, voglio aiutare! - Parametro necessario mancante. - Un parametro non è valido. - Per continuare ad usare l\'homeserver %1$s devi leggere e accettare i termini e le condizioni di utilizzo. - Leggi adesso + Manca un parametro indispensabile. + Uno dei parametri non è valido. + Per continuare ad usare l\'Home Server %1$s devi leggerne e accettarne i termini di servizio. + Leggi ora - Disattiva account - Questo renderà il tuo account permanentemente inutilizzabile. Non ci potrai più accedere e nessuno potrà ri-registrare lo stesso ID utente. Il tuo account abbandonerà tutte le stanze a cui partecipa e i dettagli del tuo account saranno rimossi dal server identità. Questa azione è irreversibile. - -Disattivare il tuo account non eliminerà in modo automatico i messaggi che hai inviato. Se vuoi che siano dimenticati i tuoi messaggi, seleziona la casella qua sotto. - -La visibilità dei messaggi in Matrix è simile alle email. \"Dimenticare i tuoi messaggi\" significa che quelli che hai inviato non verranno condivisi con alcun utente nuovo o non registrato, ma gli utenti registrati che hanno avuto accesso ai tuoi messaggi avranno ancora accesso alla loro copia. - Per favore dimenticate tutti i messaggi che ho inviato al momento della disattivazione del mio account (Attenzione: gli utenti futuri vedranno un elenco incompleto di conversazioni) + Disattiva l\'account + Ciò renderà il tuo account inutilizzabile per l\'eternità. Non potrai più accederci e nessuno potrà ri-registrare lo stesso ID utente. Il tuo account uscirà da tutte le stanze in cui si trova attualmente e di dettagli del tuo account saranno rimossi dall\'Identity Server. Questa azione è irreversibile. +\n +\nDisattivare il tuo account non eliminerà i messaggi che hai inviato. Se vuoi che i tuoi messaggi siano dimenticati seleziona la casella qua sotto. +\n +\nLa visibilità dei messaggi in Matrix è simile alle email. Questo vuol dire che se chiedi di \"dimenticare i tuoi messaggi\", quelli che hai inviato finora non verranno mai più condivisi con alcun nuovo utente, ma gli utenti che hanno già avuto accesso ai tuoi messaggi continueranno a potervi accedere. + Per favore dimenticate tutti i messaggi che ho inviato fino al momento della disattivazione del mio account (Attenzione: in questo modo i nuovi utenti potrebbero vedere delle conversazioni incomplete) Per continuare, inserisci la tua password: - Disattiva account + Disattiva l\'account Licenze di terze parti Scarica Parla Svuota - Richiedi di nuovo le chiavi di cifratura dai tuoi altri dispositivi. + Richiedi di nuovo le chiavi di crittografia dagli altri tuoi dispositivi. - Richiesta chiave inviata. + La richiesta della chiave è stata inviata. Richiesta inviata - Avvia Riot su un altro dispositivo che possa decifrare il messaggio, in modo da inviare le chiavi a questo dispositivo. + Avvia Riot su un altro dispositivo che possa decifrare il messaggio, in modo che possa inviare le chiavi a questo dispositivo. Digita qui… - Invia voce + Invia messaggio vocale prosegui con… Spiacenti, ma non è stata trovata alcuna applicazione esterna per completare l\'azione. @@ -953,37 +958,37 @@ La visibilità dei messaggi in Matrix è simile alle email. \"Dimenticare i tuoi Persone Per favore, inserisci la tua password. - Se possibile, scrivi la descrizione in inglese, grazie. + Se possibile, scrivi in inglese, grazie. Invia risposta criptata… Risposta non criptata… Mostra file multimediale prima dell\'invio - Attualmente non sei membro di alcuna comunità. + Al momento non fai parte di alcuna comunità. - Usa invio da tastiera per spedire un messaggio + Usa il tasto invio per spedire i messaggi Mostra l\'azione - Bandisci utente con un dato id - Togli il bando a un utente con un dato id - Definisci il livello di potere di un utente - Dimentica utente con un dato id - Invita utente con un dato id nella stanza corrente - Entra nella stanza con un determinato alias - Lascia la stanza - Imposta argomento della stanza - Butta fuori un utente con un dato id + Banna utente con l\'ID specificato + Togli il ban all\'utente con l\'ID specificato + Imposta i poteri di un utente + Rimuove il rango di operatore dall\'utente con l\'ID specificato + Invita l\'utente con l\'ID specificato nella stanza corrente + Entra nella stanza con un determinato nome + Esci dalla stanza + Imposta l\'argomento della stanza + Butta fuori l\'utente con l\'ID specificato Cambia il tuo nome visualizzato Markdown attivo / spento - Per correggere la gestione delle app Matrix + Per correggere la gestione dei widget - Questa stanza è stata sostituita e non è più attiva + Questa stanza è stata sostituita da un\'altra e non è più attiva La conversazione continua qui - Questa stanza è la continuazione di un\'altra conversazione - Clicca qui per vedere i messaggi più vecchi + Questa stanza contiene una conversazione cominciata altrove + Clicca qui per vedere i messaggi precedenti - Mostrare tutti i messaggi di questo utente? - -Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tempo. - A causa di permessi insufficienti, questa azione non è possibile. + Mostrare tutti i messaggi di questo utente\? +\n +\nTieni presente che questa azione riavvierà l\'app e ciò potrebbe richiedere molto tempo. + Non hai permessi sufficienti per effettuare questa azione. 1s %ds @@ -1028,31 +1033,31 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem contatta l\'amministratore del servizio - Il server home ha superato uno dei limiti delle risorse, pertanto alcuni utenti non potranno accedere. - Il server home ha superato uno dei limiti delle risorse. + L\'Home Server ha superato uno dei limiti delle risorse, pertanto alcuni utenti non potranno accedere. + L\'Home Server ha superato uno dei limiti delle risorse. - Il server home ha raggiunto il limite mensile di utenti attivi, pertanto alcuni utenti non potranno accedere. - Il server home ha raggiunto il limite mensile di utenti attivi. + L\'Home Server ha raggiunto il limite mensile di utenti attivi, pertanto alcuni utenti non potranno accedere. + L\'Home Server ha raggiunto il limite mensile di utenti attivi. Per favore %s per aumentare questo limite. Per favore %s per continuare ad usare questo servizio. Errore - Aumenta le prestazioni caricando i membri della stanza solo alla prima visualizzazione. - Il tuo server home non supporta ancora il caricamento posticipato dei membri delle stanze. Prova in seguito. + Aumenta la performance caricando i dati dei membri della stanza solo quando questi si fan vedere. + Il tuo Home Server non supporta ancora il caricamento differito dei membri delle stanze. Prova più avanti. Spiacente, si è verificato un errore - Caricamento posticipato dei membri della stanza + Caricamento differito dei membri della stanza Tema Status.im Versione %s - Crea una passphrase (frase d\'ordine) per crittografare le chiavi esportate. Dovrai inserire la stessa passphrase per poter importare le chiavi. - Crea passphrase - Le passphrase devono corrispondere + Crea una password per mettere al sicuro le chiavi esportate. La stessa password dovrà essere usata per poter importare le chiavi. + Crea una password + Le password devono corrispondere espandi - collassa + riduci Mostra l\'area informazioni Sempre @@ -1067,115 +1072,114 @@ Tieni presente che questa azione riavvierà l\'app e potrebbe richiedere del tem Chiama comunque Butta fuori - Sei sicuro di voler buttare fuori questo utente dalla chat? - Sei sicuro di voler buttare fuori questi utenti dalla chat? + Sei sicuro di voler buttare fuori questo utente\? + Sei sicuro di voler buttare fuori questi utenti\? Motivo - Visualizza anteprima dei link all\'interno della chat quando il tuo server home supporta questa funzione. + Se il tuo Home Server supporta questa funzione, all\'interno delle chat verrà visualizzata un\'anteprima dei link postati. Invia notifiche di digitazione Fai sapere agli altri utenti che stai scrivendo. - Formattazione markdown - Formatta i messaggi utilizzando la sintassi markdown prima dell\'invio. Ciò consente una formattazione avanzata come l\'uso degli asterischi per visualizzare il testo in corsivo. + Formattazione Markdown + Permette di formattare i messaggi con sintassi Markdown prima di inviarli. Ad esempio si può ottienere un testo in corsivo aggiungendovi degli asterischi prima e dopo. Mostra le conferme di lettura - Clicca sulle conferme di lettura per un elenco dettagliato. - Mostra eventi di ingresso ed uscita partecipanti - Non si applica ad inviti, butta fuori ed espulsioni. - Mostra eventi degli account + Clicca sulle conferme di lettura per vederne l\'elenco dettagliato. + Mostra entrata ed uscita degli utenti + Inviti, butta fuori e ban non vengono considerati. + Mostra i cambiamenti degli account Includi cambiamenti dell\'avatar e del nome visualizzato. Password - Avvia la fotocamera di sistema invece della schermata fotocamera personalizzata. + Avvia la fotocamera di sistema invece della fotocamera di Riot. Questa opzione richiede un\'applicazione di terze parti per registrare i messaggi. - Il comando \"%s\" necessita di più parametri, o alcuni parametri non sono validi. + Il comando \"%s\" necessita di più parametri, oppure alcuni parametri non sono corretti. Markdown è stato abilitato. Markdown è stato disabilitato. Chiamate - Usa suoneria predefinita di Riot per le chiamate in arrivo - Suoneria chiamate in arrivo - Scegli suoneria per le chiamate: + Usa la suoneria predefinita di Riot per le chiamate in arrivo + Suoneria delle chiamate in arrivo + Scegli la suoneria per le chiamate: - Accetto + Accetta - Per favore, rivedere e accettare le politiche di questo server home: + Per favore, leggi e accetta i termini di servizio di questo Home Server: - %1$d/%2$d chiave/i importate con successo. + %1$d/%2$d chiav(i) importate con successo. - Notifiche per la risoluzione dei problemi - Diagnostica per la risoluzione dei problemi + Diagnostica delle notifiche + Diagnosi Esegui i test In esecuzione… (%1$d di %2$d) La diagnostica di base risponde correttamente. Se continui a non ricevere notifiche, invia una segnalazione errore per aiutarci a indagare. Uno o più test hanno fallito, prova le soluzioni suggerite. - Uno o più test hanno fallito, per favore invia un rapporto errore per aiutarci a indagare. + Uno o più test hanno fallito, per favore invia un rapporto di errore per aiutarci a indagare. Impostazioni di sistema. - Le notifiche sono abilitate nelle impostazioni di sistema. - Le notifiche sono disabilitate nelle impostazioni di sistema. -Controlla le impostazioni di sistema. - Apri le impostazioni + Le notifiche sono abilitate nelle Impostazioni di sistema. + Le notifiche sono disabilitate nelle Impostazioni di sistema. +\nControlla le Impostazioni di sistema. + Apri le Impostazioni Impostazioni account. Le notifiche sono abilitate per il tuo account. Le notifiche sono disabilitate per il tuo account. -Controlla le impostazioni dell\'account. +\nControlla le impostazioni dell\'account. Abilita Impostazioni dispositivo. Le notifiche sono abilitate per questo dispositivo. Le notifiche non sono abilitate per questo dispositivo. -Controlla le impostazioni Riot. +\nControlla le impostazioni di Riot. Abilita - Esegui controllo servizi + Esegui un controllo dei servizi L\'APK Google Play Services è disponibile e aggiornato. - Riot usa Google Play Services per consegnare i messaggi a comparsa, ma non sembra sia configurato correttamente: + Riot usa Google Play Services per consegnare i messaggi a comparsa, ma sembra non sia stato configurato correttamente: \n%1$s - Correggi i servizi play + Correggi i Play Services Token di Firebase Token FCM recuperato con successo: %1$s - Recupero fallito del Token FCM: -%1$s + Il recupero del Token FCM: %1$s è fallito - Registrazione token - Token FCM registrato con successo sul server home. - Registrazione fallita del token FCM sul server home: -%1$s + Registrazione Token + Token FCM registrato con successo sull\'Home Server. + E\'fallita la registrazione del Token FCM sull\'Home Server: +\n %1$s Servizio di notifiche Servizio di notifiche in esecuzione. Servizio di notifiche non in esecuzione. -Prova a riavviare l\'applicazione. +\nProva a riavviare l\'applicazione. Avvia il servizio Auto riavvio del servizio di notifiche - Il servizio è stato terminato e riavviato automaticamente. - Fallito l\'avvio del servizio + Il servizio è stato chiuso e si è riavviato automaticamente. + L\'avvio del servizio è fallito Esegui all\'avvio - Il servizio sarà eseguito quando il dispositivo sarà riavviato. - Il servizio non sarà avviato al riavvio del dispositivo, non riceverai notifiche finché Riot non verrà aperto almeno una volta. + Il servizio inizierà quando il dispositivo sarà riavviato. + Il servizio non partirà al riavvio del dispositivo. Non riceverai notifiche finché Riot non verrà aperto almeno una volta. Abilita l\'esecuzione all\'avvio - Controlla le restrizioni in sottofondo - Le restrizioni in sottofondo sono disabilitate per Riot. Questo test andrebbe eseguito usando dati mobili (non WIFI). -%1$s - Le restrizioni in sottofondo sono abilitate per Riot. -Il lavoro che l\'app tenta di eseguire verrà limitato in modo aggressivo mentre è in sottofondo e ciò potrebbe influenzare le notifiche. -%1$s - Disabilita le restrizioni + Verifica se Riot sia stato configurato per funzionare in modo limitato quando lavora in background + Riot non funziona senza alcuna restrizione anche quando è eseguito in background. Questo test andrebbe eseguito usando dati mobili (non WIFI). +\n%1$s + Riot è stato configurato per funzionare in modo limitato quando è eseguito in background. +\nIl funzionamento dell\'App, quando è eseguita in background, è stato fortemente limitato e ciò potrebbe influenzare la ricezione delle notifiche. +\n%1$s + Non limitare Ottimizzazione della batteria Riot non è influenzato dall\'ottimizzazione della batteria. - Se un utente lascia un dispositivo scollegato e fermo per un periodo di tempo, a schermo spento, il dispositivo entra in modalità Doze. Ciò impedisce alle app di accedere alla rete e rinvia le attività, le sincronizzazioni e gli allarmi normali. + Se si lascia un dispositivo scollegato, fermo e con lo schermo spento, dopo un certo tempo questo entra in modalità Doze. Ciò impedisce alle App di accedere alla rete e ritarda le attività, le sincronizzazioni e la ricezione dei normali allarmi. Ignora l\'ottimizzazione - Connessione in sottofondo - Riot deve mantenere una connessione in sottofondo a basso impatto per poter ricevere notifiche affidabili. -Nella schermata successiva ti verrà chiesto di consentire a Riot di funzionare sempre in sottofondo, accetta per favore. + Connessione in background + Per poter ricevere le notifiche in tempo reale, Riot deve potersi sempre connettere. Anche quando funziona in background. +\nNella schermata successiva ti verrà chiesto di consentire a Riot di funzionare anche quando è in background, accetta per favore. Concedi il permesso Si è verificato un errore durante la verifica dell\'indirizzo email. @@ -1183,186 +1187,184 @@ Nella schermata successiva ti verrà chiesto di consentire a Riot di funzionare Si è verificato un errore durante la verifica del numero di telefono. Ulteriori informazioni: %s - Nessun valido APK Google Play Services è stato trovato. Le notifiche non funzioneranno correttamente. + Non è stato trovato nessun APK Google Play Services valido. Le notifiche non funzioneranno correttamente. Riot.im - Comunica, a modo tuo - Un\'app universale di chat sicure interamente sotto il tuo controllo. - Un\'app di chat, sotto il tuo controllo e interamente flessibile. Riot ti permette di comunicare a modo tuo. Creata per [matrix] - lo standard per le comunicazioni aperte, decentralizzate. + Una App per chat universali, sicure ed interamente sotto il tuo controllo. + Una App per chat flessibile e interamente sotto il tuo controllo. Riot ti permette di comunicare a modo tuo. Creata per [matrix] - lo standard per le comunicazioni aperte, decentralizzate. \n -\nOttieni un account matrix.org gratuito, ottieni il tuo server su https://modular.im, o usa un altro server Matrix. +\nPuoi aprire un account matrix.org gratuito, creare il tuo server su https://modular.im o usare un altro server Matrix. \n \nPerché scegliere Riot.im\? \n -\n• COMUNICAZIONE COMPLETA: crea stanze per i tuoi team, i tuoi amici, la tua comunità - come preferisci! Chatta, condividi file, aggiungi widget e fai videochiamate vocali - tutto gratuito. +\n• COMUNICAZIONE COMPLETA: crea stanze per discussione pubbliche o private per i tuoi team, i tuoi amici, la tua comunità - come preferisci! Chatta, condividi file, aggiungi widget e fai chiamate video o audio - tutto gratuito. \n -\n• GRANDI INTEGRAZIONI: usa Riot.im con gli strumenti che conosci ed ami. Con Riot.im puoi addirittura chattare con utenti e gruppi su altre app di chat. +\n• GRANDI INTEGRAZIONI: collega Riot.im alle piattaforme che conosci ed ami. Con Riot.im puoi addirittura chattare con utenti e gruppi che usano altre App di chat. \n -\n• PRIVATO E SICURO: tieni segrete le tue conversazioni. Una crittografia end-to-end allo stato dell\'arte assicura che le comunicazioni private restino tali. +\n• PRIVATO E SICURO: tieni segrete le tue conversazioni. Una crittografia end-to-end allo stato dell\'arte assicura che le comunicazioni private restino sempre tali. \n -\n• OPEN, NON CHIUSO: open source e costruito su Matrix. Possiedi i tuoi dati ospitando il tuo server personale, o scegliendone uno di cui ti fidi. +\n• OPEN, NON CHIUSO: open source e costruito su Matrix. Riprendi il possesso dei tuoi dati creando il tuo server Matrix personale, o scegliendone uno di cui ti fidi. \n -\n• OVUNQUE TU SIA: resta in contatto ovunque tu sia con la cronologia dei messaggi totalmente sincronizzata tra i tuoi dispositivi ed online su https://riot.im. +\n• OVUNQUE TU SIA: stai sul pezzo ovunque tu sia grazie la cronologia dei messaggi sempre sincronizzata tra i tuoi dispositivi ed online su https://riot.im. - Videochiamata in corso… + Chiamata video in corso… - "Apportiamo continuamente cambiamenti e migliorie a Riot.im. -Il registro completo delle modifiche si può trovare qui: %1$s. -Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi." - Backup chiave - Usa backup chiave - Il backup delle chiavi non è concluso, attendere prego… + Riot.im continua a crescere e migliorare. Il registro completo delle modifiche si può trovare qui: %1$s. Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi. + Backup delle chiavi + Usa il Backup delle chiavi + Il Backup delle chiavi non è concluso, attendere prego… Salta Fatto Impostazioni di notifica avanzate - Perderai i tuoi messaggi cifrati se ti disconnetti adesso - Backup chiave in corso. Se ti disconnetti ora, perderai l\'accesso ai tuoi messaggi cifrati. - Il Backup Chiave Sicuro deve essere attivo su tutti i tuoi dispositivi per evitare di perdere l\'accesso ai tuoi messaggi cifrati. + Se ti disconnetti adesso perderai i tuoi messaggi cifrati + Il Backup delle chiavi è in corso. Se ti disconnetti adesso perderai i tuoi messaggi cifrati. + Il Backup delle chiavi deve essere attivo su tutti i tuoi dispositivi per evitare di perdere l\'accesso ai tuoi messaggi cifrati. Non voglio i miei messaggi cifrati Backup delle chiavi… - Usa backup chiave + Usa il Backup delle chiavi Sei sicuro\? Backup - Perderai l\'accesso ai tuoi messaggi cifrati a meno che non fai il backup delle tue chiavi prima di disconnetterti. + Perderai l\'accesso ai tuoi messaggi cifrati a meno che tu non faccia il Backup delle chiavi prima di disconnetterti. - Resta + Rimani Interrompi Ignora Sei sicuro di volerti disconnettere\? Accedi con single sign-on - Questo URL è irraggiungibile, controllalo - Il tuo dispositivo sta usando un protocollo di sicurezza TLS obsoleto, vulnerabile ad attacchi. Per la tua sicurezza non potrai connetterti - Imposta l\'importanza della notifica per evento, configura il suono, il led, la vibrazione - Importanza della notifica per evento + Questo URL è irraggiungibile. Per favore controllalo + "Il tuo dispositivo usa un protocollo di sicurezza TLS obsoleto e vulnerabile agli attacchi. Per la tua sicurezza, ti viene impedito di connetterti" + Imposta i livelli di importanza delle notifiche in base al tipo. Configura i suoni, i led, e la vibrazione + Importanza delle notifiche in base al tipo Impostazioni personalizzate. - Nota che alcuni tipi di messaggio sono impostati come silenziosi (produrrà una notifica senza suono). + Nota che alcuni tipi di messaggio sono impostati come silenziosi (ossia generano notifiche senza suono). Alcune notifiche sono disattivate nelle tue impostazioni personalizzate. - Caricamento regole personalizzate fallito, riprova. - Controlla impostazioni + Il caricamento delle regole personalizzate è fallito, riprova. + Controlla le Impostazioni - [%1$s] -\nQuesto errore è fuori dal controllo di Riot e secondo Google, questo errore indica che il dispositivo ha troppe app registrate con FCM. L\'errore si verifica solo in casi in cui ci sia un numero estremo di app, quindi non dovrebbe affliggere l\'utente medio. - [%1$s] -\nQuesto errore è fuori dal controllo di Riot. Può accadere per vari motivi. Potrebbe funzionare se riprovi più tardi, puoi anche controllare che Google Play Service non ha limitazioni di utilizzo di dati nelle impostazioni di sistema, o che l\'orologio del tuo dispositivo è corretto, o può succedere in custom ROM. - [%1$s] -\nQuesto errore è fuori dal controllo di Riot. Non c\'è alcun account Google nel telefono. Apri il gestore di account ed aggiungi un account Google. + [%1$s] +\nQuesto errore non dipende da Riot. Secondo Google dipende dal fatto che questo dispositivo ha troppe App registrate con FCM. L\'errore si verifica solo in casi in cui ci sia un numero estremo di app, quindi non dovrebbe affliggere l\'utente medio. + [%1$s] +\nQuesto errore non dipende da Riot e può avere diverse cause. Potresti riprovare più tardi o controllare che Google Play Service non abbia configurato nelle Impostazioni di sistema dei limiti di utilizzo di dati. Anche un orologio di sistema regolato male potrebbe esserne la causa. Oppure può verificarsi se hai una ROM customizzata. + [%1$s] +\nQuesto errore non dipende da Riot. Non c\'è alcun account Google nel telefono. Apri il gestore di account ed aggiungi un account Google. Aggiungi account - Configura notifiche rumorose - Configura notifiche chiamata - Configura notifiche silenziose - Scegli colore del LED, vibrazione, suono… + Configura le notifiche rumorose + Configura le notifiche di chiamata + Configura le notifiche silenziose + Scegli il colore del LED, la vibrazione, il suono… Gestione chiavi crittografiche - Invia messaggio con tasto Invio - Il tasto Invio della tastiera invierà il messaggio invece di andare a capo + Invia il messaggio col tasto Invio + Il tasto Invio della tastiera, invece di andare a capo invierà il messaggio - La modalità risparmio dati applica un filtro specifico in modo che gli aggiornamenti di presenza e le notifiche di scrittura sono esclusi. + La modalità risparmio dati applica un filtro specifico che esclude gli aggiornamenti di presenza e le notifiche di scrittura. - Aggiorna password + Aggiorna la password La password non è valida Le password non corrispondono - Ripristino messaggi cifrati - Gestisci backup chiave + Ripristino dei messaggi cifrati + Gestisci il Backup delle chiavi Silenzioso Inserisci un nome utente. Inserisci una password La password è troppo debole - Elimina la password se vuoi che Riot generi una chiave di recupero. - Nessuna sessione Matrix disponibile + Cancella la password se vuoi che Riot generi un codice di recupero. + Non c\'è alcuna sessione Matrix disponibile Non perdere mai i messaggi cifrati - I messaggi nelle stanze cifrate sono protetti con la cifratura end-to-end. Solo tu e il/i destinatario/i avete le chiavi per leggere questi messaggi. -\n -\nFai un backup sicuro delle tue chiavi per evitare di perderle. - Inizia ad usare il backup chiavi + I messaggi nelle stanze cifrate sono protetti con crittografia E2E. Solo tu e il/i destinatario/i avete le chiavi crittografiche per leggere questi messaggi. +\n +\nRicorda di fare un backup delle tue chiavi crittografiche per evitare di perderle. + Inizia ad usare il Backup delle chiavi crittografiche (Avanzato) - Esporta manualmente le chiavi + Esporta manualmente le chiavi crittografiche Proteggi il tuo backup con una password. - Salveremo una copia cifrata delle tue chiavi nel tuo homeserver. Proteggi il tuo backup con una password per tenerlo sicuro. -\n -\nPer massima sicurezza, dovrebbe essere diversa dalla password del tuo account. + Una copia cifrata delle tue chiavi crittografiche sarà salvata sul tuo Home Server. Proteggi il tuo Backup con una password per tenerlo al sicuro. +\n +\nPer una maggior sicurezza, è meglio che la password del Backup delle chiavi sia diversa da quella del tuo account. Imposta password Creazione backup - Oppure, proteggi il tuo backup con una chiave di ripristino, salvandola in un luogo sicuro. - (Avanzato) Imposta con chiave di ripristino - Completato! - Backup delle tue chiavi in corso. - La tua chiave di ripristino è una rete di sicurezza - puoi usarla per recuperare l\'accesso ai tuoi messaggi cifrati se dimentichi la password. -\nTieni la tua chiave di ripristino in un luogo sicuro, come un password manager (o una cassaforte) - Tieni la tua chiave di ripristino in un luogo sicuro, come un password manager (o una cassaforte) + Oppure, proteggi il tuo Backup con un codice di recupero, salvandolo in un luogo sicuro. + (Avanzato) Imposta un codice di recupero + Fatto! + Il Backup delle tue chiavi crittografiche è in corso. + Il tuo codice di recupero è un\'ancora di salvezza - puoi usarlo per riaccedere ai tuoi messaggi cifrati se dimentichi la password. +\nSalva il tuo codice di recupero in un luogo sicuro, tipo un password manager (o una cassaforte) + Salva il tuo codice di recupero in un luogo sicuro, come un password manager (o una cassaforte) Fatto Ho fatto una copia - Salva chiave di ripristino + Salva il codice di recupero Condividi Salva come file - La chiave di ripristino è stata salvata in \'%s\'. -\n -\nAttenzione: questo file potrebbe essere eliminato se viene disinstallata l\'applicazione. + Il codice di recupero è stato salvato in \'%s\'. +\n +\nAttenzione: se l\'applicazione su cui è salvato venisse disinstallata, il codice di recupero verrà perso. Si prega di farne una copia - Condividi chiave di ripristino con… - Generazione chiave di ripristino usando la password, questo processo può durare diversi secondi. - Chiave di ripristino + Condividi il codice di recupero con… + Generazione del codice di recupero usando la password. Questo processo può durare alcuni secondi. + Codice di recupero Errore inatteso - Backup iniziato - Backup in corso in secondo piano delle tue chiavi di ripristino nel tuo homeserver. Il backup iniziale può durare diversi minuti. + Backup avviato + Il tuo Home Server sta facendo il Backup delle tue chiavi crittografiche in background. Il primo Backup può impiegare diversi minuti. Sei sicuro\? - Potresti perdere l\'accesso ai tuoi messaggi se ti disconnetti o perdi il dispositivo. + Se ti disconnetti o perdessi questo dispositivo potresti perdere l\'accesso ai tuoi messaggi. - Rilevazione versione backup… - Usa la tua password di ripristino per sbloccare la cronologia dei messaggi cifrati - usa la tua chiave di ripristino - Non conosci la tua password di ripristino, puoi %s. + Verifica versione backup… + Usa la tua password di recupero per sbloccare i messaggi cifrati + usa il tuo codice di recupero + Se non conosci la tua password di recupero, puoi %s. - Usa la tua chiave di ripristino per sbloccare la cronologia dei messaggi cifrati - Inserisci chiave di ripristino + Usa il tuo codice di recupero per sbloccare la Timeline dei messaggi cifrati + Inserisci codice di recupero Ripristino messaggio - Hai perso la chiave di ripristino\? Puoi crearne una nuova nelle impostazioni. - Impossibile decifrare il backup con questa password: verifica di avere inserito la password di ripristino corretta. + Hai perso il codice di recupero\? Nelle Impostazioni puoi crearne uno nuovo. + Impossibile decifrare il backup con questa password: verifica che la password di recupero inserita sia corretta. Errore di rete: controlla la tua connessione e riprova. - Ripristino backup: - Calcolo chiave di ripristino… - Scaricamento chiavi… + Ripristino Backup: + Codice di recupero in elaborazione… + Download chiavi… Importazione chiavi… - Sblocca cronologia - Inserisci una chiave di ripristino - Impossibile decifrare il backup con questa chiave di ripristino: verifica di avere inserito la chiave di ripristino corretta. + Sblocca Timeline + Inserisci un codice di recupero + Impossibile decifrare il backup con questo codice di recupero: verifica di avere inserito il codice di recupero corretto. Backup ripristinato %s ! - Ripristinate %1$d chiavi di sessione e aggiunte %2$d nuove chiavi sconosciute a questo dispositivo + Ripristinate %1$d Session-Keys e aggiunte %2$d nuove Key(s) finora sconosciute a questo dispositivo - Ripristinato un backup con %d chiave. - Ripristinato un backup con %d chiavi. + Ripristinato un Backup con %d chiave. + Ripristinato un Backup con %d chiavi. %d nuova chiave è stata aggiunta a questo dispositivo. %d nuove chiavi sono state aggiunte a questo dispositivo. - Rilevazione ultima versione chiavi di ripristino fallita (%s). - La cifratura di sessione non è attivata + Rilevazione ultima versione codici di recupero fallita (%s). + La cifratura della sessione non è stata attivata Ripristina da backup Elimina backup - Backup chiavi impostato correttamente per questo dispositivo. - Backup chiavi non attivo per questo dispositivo. - Nessun backup programmato per le chiavi da questo dispositivo. + Il Backup delle chiavi è stato impostato correttamente su questo dispositivo. + Il Backup delle chiavi non è attivo su questo dispositivo. + Questo dispositivo non sta facendo il Backup delle chiavi. Il backup ha una firma da un dispositivo sconosciuto con ID %s. Il backup ha una firma valida da questo dispositivo. @@ -1372,30 +1374,30 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Il backup ha una firma non valida dal dispositivo non verificato %s Impossibile ottenere info di fiducia per il backup (%s). - Per usare il backup chiavi su questo dispositivo, recupera con la password o chiave di ripristino ora. + Per utilizzare il Backup delle chiavi crittografiche su questo dispositivo, devi accedervi con la password o con il codice di recupero. Eliminazione backup… Eliminazione backup fallita (%s) Elimina backup - Eliminare dal server il backup delle chiavi di cifratura\? Non potrai più usare la tua chiave di ripristino per leggere la cronologia di messaggi cifrati. + Eliminare il Backup delle chiavi crittografiche dall\'Home Server\? Non potrai più usare il codice di recupero per leggere i messaggi cifrati. - Nessun backup chiavi - È stato rilevato un nuovo backup chiavi per messaggi sicuri. -\n -\nSe non hai impostato il nuovo metodo di ripristino, potrebbe essere un tentativo di un aggressore di accedere al tuo account. Cambia la tua password e imposta un nuovo metodo di ripristino immediatamente nelle impostazioni. + Nuovo Backup delle chiavi + È stato rilevato un nuovo Backup delle chiavi crittografiche. +\n +\nSe non hai già configurato il nuovo metodo di ripristino, potrebbe trattarsi di un tentativo di un aggressore. Cambia subito la tua password e configura un nuovo metodo di ripristino nelle Impostazioni. Sono stato io Non perdere mai i messaggi cifrati - Inizia ad usare il backup chiavi + Inizia ad usare il Backup delle chiavi Non perdere mai i messaggi cifrati - Usa backup chiavi + Usa il Backup delle chiavi - Nuove chiavi di messaggi cifrati - Gestisci in backup chiavi + Nuove chiavi di crittografia + Gestisci nel Backup delle chiavi - Backup chiavi… + Backup chiavi in corso… - Backup fatto di tutte le chiavi + Il Backup delle chiavi è stato eseguito Backup di %d chiave… Backup di %d chiavi… @@ -1405,7 +1407,7 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Algoritmo Firma - Risposta alla scoperta di un homeserver non valida + Risposta Home Server non valida Opzioni autocompletamento server Riot ha rilevato una configurazione server personalizzata per il tuo dominio userId \"%1$s\": \n%2$s @@ -1417,10 +1419,10 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Scegli Sorgente multimediale predefinita Scegli - Riproduci suono otturatore + Riproduci il suono dell\'otturatore Segna come letto - L\'app non ha bisogno di connettersi in background all\'homeserver, dovrebbe ridurre il consumo della batteria + L\'app non ha bisogno di connettersi in background all\'Home Server. Ciò dovrebbe ridurre il consumo della batteria %1$s: 1 messaggio %1$s: %2$d messaggi @@ -1437,23 +1439,23 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Io ** Invio fallito - per favore apri la stanza - Spiacenti, le chiamate di gruppo con Jitsi non sono supportate sui vecchi dispositivi (con Android precedenti al 5.0) + Purtroppo i vecchi dispositivi (quelli con Android precedenti al 5.0) non supportano le conferenze con Jitsi Verifica dispositivo IP sconosciuto - Un nuovo dispositivo sta richiedendo le chiavi di cifratura. -\nNome dispositivo: %1$s -\nUltimo accesso: %2$s -\nSe non hai effettuato l\'accesso in un altro dispositivo, ignora questa richiesta. - Un dispositivo non verificato sta richiedendo le chiavi di cifratura. -\nNome dispositivo: %1$s -\nUltimo accesso: %2$s -\nSe non hai effettuato l\'accesso in un altro dispositivo, ignora questa richiesta. + Un nuovo dispositivo sta chiedendo le chiavi crittografiche. +\nNome dispositivo: %1$s +\nUltimo accesso: %2$s +\nSe non hai effettuato l\'accesso da un altro dispositivo, ignora questa richiesta. + Un dispositivo non verificato sta chiedendo le chiavi crittografiche. +\nNome dispositivo: %1$s +\nUltimo accesso: %2$s +\nSe non hai effettuato l\'accesso da un altro dispositivo, ignora questa richiesta. Verifica Condividi - Richiesta condivisione chiave + Richiesta condivisione chiavi crittografiche Ignora Verifica confrontando una breve stringa. @@ -1497,12 +1499,12 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Il SAS non corrispondeva Il dispositivo ha ricevuto un messaggio inaspettato È stato ricevuto un messaggio non valido - Chiave non corrispondente + Le chiavi non corrispondono Utente non corrispondente Errore sconosciuto - Esiste già un backup sul tuo HomeServer - Sembra che tu abbia già impostato un backup chiavi da un altro dispositivo. Vuoi sostituirlo con quello che stai creando\? + Esiste già un backup sul tuo Home Server + Sembra tu abbia già impostato il Backup delle chiavi crittografiche da un altro dispositivo. Vuoi sostituirlo con il Backup che stai creando\? Sostituisci Ferma @@ -1521,7 +1523,7 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Benvenuti a casa! Recupera i messaggi non letti qui Conversazioni - La tua conversazione diretta verrà mostrata qui + Le tue conversazioni dirette verranno mostrate qui Stanze Le tue stanze verranno mostrate qui @@ -1560,7 +1562,7 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Pubblica questa stanza nell\'elenco stanze Si è verificato un errore nell\'ottenere informazioni sulla fiducia - Si è verificato un errore nell\'ottenere i dati del backup chiavi + Si è verificato un errore nell\'ottenere i dati dal Backup delle chiavi Benvenuti nella beta! Mentre RiotX si trova nella fase iniziale dello sviluppo, potrebbero mancare alcune funzioni e potrebbero verificarsi errori. @@ -1568,7 +1570,7 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Descrizione nel Play Store Se trovi errori ti preghiamo segnalarli nel menu in alto a sinistra della Pagina Iniziale e noi li correggeremo appena possibile. - Importa chiavi e2e dal file \"%1$s\". + Importa le chiavi di crittografia dal file \"%1$s\". Versione SDK Matrix Altre note di terze parti @@ -1591,7 +1593,7 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Url: Formato: - Voce e video + Audio e Video Aiuto e informazioni @@ -1603,16 +1605,16 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Grazie, il suggerimento è stato inviato correttamente L\'invio del suggerimento è fallito (%s) - Mostra eventi nascosti nella cronologia + Mostra gli eventi nascosti nella Timeline RiotX - Client Matrix di nuova generazione - Un client per Matrix più veloce e leggero usando gli ultimi framework di Android - RiotX è un nuovo client per il protocollo Matrix (Matrix.org): una rete aperta per comunicazioni sicure e decentralizzate. RiotX è una riscrittura totale del client Riot Android, basata su una riscrittura completa dell\'SDK Android di Matrix. -\n -\nDisclaimer: questa è una versione beta. RiotX è attualmente in uno sviluppo attivo e contiene limitazioni ed errori (speriamo non troppi). I suggerimenti sono ben accetti! -\n -\nRiotX supporta: • Accesso ad account esistente • Crea stanze ed entra in stanze pubbliche • Accetta e rifiuta inviti • Elenca stanze utenti • Vedi dettagli stanza • Invia messaggi di testo • Invia allegati • Leggi e scrivi messaggi in stanze cifrate • Crypto: backup chiavi E2E, verifica avanzata dispositivi, richiesta e risposta condivisione chiavi • Notifiche push • Tema chiaro, scuro e nero -\n + Un client per Matrix più veloce e leggero che utilizza gli ultimi framework di Android + RiotX è un nuovo client per il protocollo Matrix (Matrix.org): una rete aperta per comunicazioni sicure e decentralizzate. RiotX è stato completamente riscritto rispetto a Riot Android, ed è a sua volta basato su un SDK Android di Matrix totalmente riscritto. +\n +\nDisclaimer: questa è una versione beta. RiotX è attualmente in uno sviluppo attivo e contiene ancora alcune limitazioni ed errori (speriamo non troppi). I suggerimenti sono ben accetti! +\n +\nRiotX supporta: • Accesso ad account esistente • Crea stanze ed entra in stanze pubbliche • Accetta e rifiuta inviti • Elenca stanze utenti • Vedi dettagli stanza • Invia messaggi di testo • Invia allegati • Leggi e scrivi messaggi in stanze cifrate • Crypto: backup chiavi E2E, verifica avanzata dispositivi, richiesta e risposta condivisione chiavi • Notifiche push • Tema chiaro, scuro e nero +\n \nNon tutte le funzioni di Riot sono già implementate in RiotX. Principali funzioni mancanti (prossimamente!): • Creazione account • Impostazioni stanza (elenca membri stanza, ecc.) • Chiamate • Widget • … Non hai nulla di nuovo da vedere! @@ -1642,127 +1644,127 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Invia un nuovo messaggio diretto Vedi l\'elenco delle stanze - Nome o ID (#esempio:matrix.org) + Nome o ID stanza (#esempio:matrix.org) Attiva swipe per rispondere nella timeline Collegamento copiato negli appunti - Gestore dell\'integrazione + Integration Manager - Nessun gestore integrazione configurato. - Aggiungi per ID matrix + Non è stato configurato nessun Integration Manager. + Aggiungi per ID utente Creare una stanza … - Nessun risultato trovato, usa \"Aggiungi per ID matrix\" per cercare sul server. + Nessun risultato trovato. Usa \"Aggiungi per ID utente\" per cercare sul server. Inizia a digitare per ottenere risultati - Filtra per nome utente o ID … + Cerca per nome o ID utente … Sto entrando nella stanza … - Visualizza Modifica cronologia + Visualizza Modifica Timeline Leggi Rifiuta - Per continuare, devi accettare i termini di servizio. + Per continuare devi accettare i termini di servizio. - Le precedenti versioni di Riot avevano un errore di sicurezza che poteva dare accesso al tuo account al tuo Server di Identità (%1$s). Se ti fidi di %2$s, puoi ignorare; altrimenti disconnettiti e riaccedi di nuovo. -\n -\nLeggi maggiori dettagli qui: + Alcune precedenti versioni di Riot contenevano un errore di sicurezza che permetteva al tuo Identity Server (%1$s) di accedere al tuo account. Se ti fidi di %2$s, puoi ignorare questo avviso; altrimenti disconnettiti e riaccedi di nuovo. +\n +\nLeggi maggiori dettagli qui: \nhttps://medium.com/@RiotChat/36b4792ea0d6 Termini di servizio - Leggi i termini - Diventa trovabile dagli altri - Usa bot, bridge, widget e pacchetti di adesivi + Leggi i termini di servizio + Fatti trovare dagli altri utenti + Usa bot, bridge, widget e pacchetti di sticker Leggi su Nessuno Revoca Disconnetti - Nessun server di identità configurato. + Non è stato configurato alcun Identity Server. - Chiamata fallita per server malconfigurato - Chiedi all\'amministratore del tuo homeserver (%1$s) di configurare un server TURN affinché le chiamate funzionino in modo affidabile. -\n -\nAltrimenti, puoi provare ad usare il server pubblico su %2$s, ma non è ugualmente affidabile e vedrà il tuo indirizzo IP. Puoi fare la tua scelta anche nelle Impostazioni. + Chiamata fallita a causa di un\'errata configurazione del server + Chiedi all\'amministratore del tuo Home Server (%1$s) di configurare un server TURN affinché le chiamate funzionino in modo affidabile. +\n +\nOppure puoi provare ad usare il server pubblico su %2$s, ma non sarà altrettanto affidabile e potrà vedere il tuo indirizzo IP. Puoi configurare tutto questo nelle Impostazioni. Prova ad usare %s Non chiedermelo più - Imposta un\'email per il recupero dell\'account, o più tardi per essere trovabile dalle persone che ti conoscono. - Imposta un telefono, o più tardi per essere trovabile dalle persone che ti conoscono. - Imposta un\'email per il recupero dell\'account. Più tardi usa email o telefono per essere trovabile dalle persone che ti conoscono. - Imposta un\'email per il recupero dell\'account. Più tardi usa email o telefono per essere trovabile dalle persone che ti conoscono. - Impossibile raggiungere un homeserver con questo URL, controllalo - Permetti server di assistenza chiamata di riserva - Userà %s come assistente quando l\'homeserver non ne offre uno (il tuo indirizzo IP sarà visibile durante le chiamate) - Aggiungi un server di identità nelle impostazioni per poterlo fare. + Imposta un\'email per il ripristino dell\'account in caso di problemi e, se vuoi, anche per farti trovare da chi conosce quell\'indirizzo email. + Aggiungi un numero di telefono se vuoi farti trovare da chi lo conosce. + Imposta un\'email per il ripristino dell\'account in caso di problemi. Email e telefono potranno essere usati anche per farti trovare dagli altri utenti. + Imposta un\'email per il ripristino dell\'account in caso di problemi. Email e numero di telefono potranno essere usati, se lo vorrai, anche per permettere a chi li conosce di trovarti. + Non è stato trovato alcun Home Server seguendo questo URL. Per favore, controllalo + Permetti chiamate dal Server di appoggio + "Se il tuo Home Server non ne ha un proprio Server d\'appoggio verrà usato %s (il Server d\'appoggio verrà a conoscenza del tuo indirizzo IP durante le chiamate)" + Per poterlo fare, aggiungi un Identity Server nelle Impostazioni. Modalità sync in background (Sperimentale) Ottimizzato per la batteria - Riot sincronizzerà in background in modo da preservare le risorse limitate del dispositivo (batteria). -\nA seconda dello stato delle risorse del tuo dispositivo, la sincronizzazione può essere ritardata dal sistema operativo. - Ottimizzato per il tempo reale - Riot sincronizzerà in background periodicamente in momenti precisi (configurabile). -\nCiò avrà impatto sull\'uso di dati e batteria, ci sarà una notifica permanente per comunicare che Riot è in attesa di eventi. - Nessun sync in background - Non ti verranno notificati i messaggi in arrivo quando l\'app è in background. - Aggiornamento impostazioni fallito. + Riot si sincronizzerà in background in modo da non consumare la poca batteria disponibile. +\nA seconda del livello della batteria, il sistema operativo potrebbe ritardare la sincronizzazione. + Ottimizzato per la performance + Riot si sincronizzerà in background ad intervalli regolari (configurabili). +\nCiò avrà un certo impatto sulla quantità di dati e batteria utilizzati. Una notifica sempre accesa comunicherà che Riot è attivo. + Nessuna sincronizzazione in background + Quando l\'App è in background non ti verranno notificati i messaggi in arrivo. + L\'aggiornamento delle impostazioni è fallito. - Intervallo di sync preferito + Intervallo preferito tra le sincronizzazioni %s \nLa sincronizzazione può essere ritardata a seconda delle risorse (batteria) o dello stato del dispositivo (sospensione). - Scoperte - Gestisci le tue impostazioni di rilevamento. + Farsi trovare + Configura l\'Identity Server per trovare e farsi trovare dagli altri utenti. Nome pubblico (visibile alle persone con cui comunichi) Il nome pubblico di un dispositivo è visibile alle persone con cui comunichi - Non stai usando alcun server di identità - Nessun server di identità configurato, è necessario per ripristinare la tua password. + Non stai usando alcun Identity Server + Nessun Identity Server configurato. E\' necessario per poter ripristinare la tua password. Pare che tu stia provando a connetterti ad un altro homeserver. Vuoi disconnetterti da qui\? - Server di identità - Disconnetti server di identità - Configura server di identità - Cambia server di identità - Attualmente stai usando %1$s per scoprire ed essere trovabile dai contatti esistenti che conosci. - Attualmente non stai usando alcun server di identità. Per scoprire ed essere trovabile dai contatti esistenti che conosci, configurane uno qua sotto. - Indirizzi email trovabili - Le opzioni di scoperta appariranno dopo avere aggiunto un\'email. - Le opzioni di scoperta appariranno dopo avere aggiunto un numero di telefono. - Se ti disconnetti dal server di identità non potrai più essere trovato da altri utenti e non potrai invitarne altri per email o telefono. - Numeri di telefono trovabili - Ti abbiamo inviato un\'email di conferma a %s, controlla l\'email e clicca sul link di conferma + Identity Server + Disconnetti Identity Server + Configura Identity Server + Cambia Identity Server + Stai usando l\'Identity Server %1$s per trovare e farti trovare da altri utenti. + In questo momento non stai usando alcun Identity Server. Per trovare e farti trovare dagli altri utenti, configurane uno qua sotto. + Indirizzi email visibili pubblicamente + Le opzioni su come farsi trovare da altri utenti appariranno dopo avere aggiunto un\'email. + Le opzioni su come farsi trovare da altri utenti appariranno dopo avere aggiunto un numero di telefono. + Se ti disconnetti dall\'Identity Server gli altri utenti non potranno trovarti e tu non potrai invitarne di nuovi per email o telefono. + Numeri di telefono visibili pubblicamente + Abbiamo inviato un\'email di conferma a %s, controlla l\'email e clicca sul link di conferma In attesa - Inserisci un nuovo server di identità - Impossibile connettersi al server di identità - Inserisci l\'URL del server di identità - Il server di identità non ha condizioni di servizio - Il server di identità che hai scelto non ha alcuna condizione di servizio. Continua solo se ti fidi del proprietario del servizio - È stato inviato un messaggio a %s. Inserisci il codice di verifica contenuto. + Inserisci un nuovo Identity Server + Impossibile connettersi all\'Identity Server + Inserisci l\'URL dell\'Identity Server + L\'Identity Server non ha reso noti i propri termini di servizio + L\'Identity Server che hai scelto non ha freso noti i propri termini di servizio. Continua solo se ti fidi + È stato inviato un messaggio a %s. Inserisci il codice di verifica che contiene. - Attualmente stai condividendo indirizzi email o numeri di telefono sul server di identità %1$s. Dovrai riconnetterti a %2$s per fermarne la condivisione. - Accetta le condizioni di servizio del server di identità (%s) per consentire di essere trovabile per email o numero di telefono. + In questo momento stai condividendo i tuoi indirizzi email o numeri di telefono sull\'Identity Server %1$s. Dovrai riconnetterti a %2$s per interromperne la condivisione. + Accetta i termini di servizio dell\'Identity Server (%s) per permettere ad altri utenti di trovarti tramite la tua email o numero di telefono. Latn - Attiva log dettagliati. - I log dettagliati aiuteranno gli sviluppatori fornendo loro più informazioni quando invii una segnalazione. Anche quando attivi, l\'applicazione non registra i contenuti dei messaggi o altri dati personali. + Attiva i log dettagliati. + I log dettagliati aiuteranno gli sviluppatori fornendo loro molte più informazioni nelle segnalazioni che invii scuotendo il dispositivo. Anche se attivi i log dettagliati, Riot non registra mai i contenuti dei messaggi o altri dati personali. - Riprova dopo avere accettato i termini e condizioni del tuo homeserver. + Riprova dopo avere accettato i termini di servizio del tuo Home Server. - Sembra che il server stia impiegando troppo tempo a rispondere, ciò può essere causato da scarsa connettività o un errore con i nostri server. Riprova fra qualche minuto. + Sembra che il server stia impiegando troppo tempo a rispondere. Ciò può essere causato da una cattiva connessione o da un errore del server. Riprova fra qualche minuto. Invia allegato Apri il pannello di navigazione - Apri il menu di creazione stanza - Chiudi il menu di creazione stanza… + Apri il menu \"Crea nuova stanza\" + Chiudi il menu di \"Crea nuova stanza\"… Crea una nuova conversazione diretta - Crea una nuova stanza - Chiudi il banner di backup chiavi + Crea nuova stanza + Chiudi il banner del Backup delle chiavi Mostra password Nascondi password Salta in fondo @@ -1784,8 +1786,8 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi."Fotocamera Audio Galleria - Adesivo - Errore di gestione dati condivisi + Sticker + Errore nella gestione dei dati condivisi È spam È inappropriato @@ -1808,10 +1810,45 @@ Per essere certo di non perdere nulla, mantieni gli aggiornamenti attivi." - Riot richiede l\'autorizzazione per salvare le tue chiavi E2E su disco. -\n + Riot richiede l\'autorizzazione per salvare le tue chiavi crittografiche sul disco. +\n \nPermetti l\'accesso nel prossimo pop-up per poter esportare le chiavi manualmente. - Non c\'è nessuna connessione di rete al momento + In questo momento non c\'è nessuna connessione di rete + + Conferma la tua password + Non puoi farlo da Riot mobile + E\'necessaria l\'autenticazione + + + Integrazioni + Usa un Integration Manager per gestire bot, bridge, widget e pacchetti di sticker. +\nGli Integration Manager possono ricevere dati di configurazione, modificare widget, mandare inviti alle stanze e modificare permessi a tuo nome. + Permetti l\'uso di integrazioni + Widget + Carica widget + Questo widget è stato aggiunto da: + Utilizzandolo potrebbero esser impostati dei cookie e dei dati potrebbero essere condivisi con %s: + Utilizzandolo potrebbero essere condivisi dei dati con %s: + Il caricamento del widget è fallito. +\n%s + Ricarica widget + Apri nel browser + Revoca l\'accesso per me + + Il tuo nome visualizzato + L\'URL del tuo avatar + Il tuo ID utente + Il tuo tema + ID widget + ID stanza + + + Questo widget vuole usare le seguenti risorse: + Permetti + Blocca tutto + Usare la fotocamera + Usa il microfono + Leggi media protetti da DRM diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 10338ddde9..f2ff6691f3 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -1158,7 +1158,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Ju lutemi, jepni një frazëkalim Frazëkalimi është shumë i dobët - Ju lutemi, fshini frazëkalimin, nëse doni që Riot-i të prodhojë një kyç rikthimesh. + Ju lutemi, fshini frazëkalimin, nëse doni që Riot-i të prodhojë një kyç rimarrjesh. S’ka sesione Matrix të gatshëm Mos humbni kurrë mesazhe të fshehtëzuar @@ -1167,16 +1167,16 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani \nBëni një kopjeruajtje të sigurt të kyçeve tuaj, për të shmangur humbjen e tyre. Caktoni Frazëkalim U bë - Ruani Kyç Rikthimesh + Ruani Kyç Rimarrjesh Ruaje si Skedë - Kyçi i rikthimeve u ruajt te \'%s\'. -\n + Kyçi i rimarrjeve u ruajt te \'%s\'. +\n \nKujdes: kjo kartelë mund të fshihet, nëse çinstalohet aplikacioni. Ju lutemi, bëni një kopje - Jepjani kyçin e rikthimeve… - Po prodhohet Kyç Rikthimesh duke përdorur frazëkalim, ky proces mund të hajë disa sekonda. - Kyç Rikthimesh + Jepjani kyçin e rimarrjeve… + Po prodhohet Kyç Rimarrjesh duke përdorur frazëkalim, ky proces mund të hajë disa sekonda. + Kyç Rimarrjesh Gabim i papritur Nisi Kopjeruajtja Te shërbyesi juaj Home, tani po krijohet në prapaskenë kopjeruajtje e kyçeve tuaj të fshehtëzimit. Kopjeruajtja fillestare mund të dojë disa minuta. @@ -1186,23 +1186,23 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Nëse dilni nga llogaria, ose nëse humbni pajisjen tuaj, mund të humbni hyrjen në mesazhet tuaj. Po sillet version kopjeruajtjeje… - Që të shkyçni historikun e mesazheve tuaj të sigurt, përdorni frazëkalimin tuaj të rikthimeve - përdorni kyçin tuaj të rikthimeve - S’dihet frazëkalimi juaj i rikthimeve, mundeni të %s. + Që të shkyçni historikun e mesazheve tuaj të sigurt, përdorni frazëkalimin tuaj të rimarrjeve + përdorni kyçin tuaj të rimarrjeve + S’dihet frazëkalimi juaj i rimarrjeve, mundeni të %s. - Përdorni Kyçin tuaj të Rikthimeve për të shkyçur historikun tuaj të mesazheve të sigurt - Jepni Kyç Rikthimesh + Përdorni Kyçin tuaj të Rimarrjeve për të shkyçur historikun tuaj të mesazheve të sigurt + Jepni Kyç Rimarrjesh - Rikthim Mesazhesh + Rimarrje Mesazhesh - Humbët kyçin tuaj të rikthimeve? Te rregullimet mund të caktoni një të ri. - S’u shfshehtëzua dot kopjeruajtja me këtë frazëkalim: ju lutemi, verifikoni që dhatë frazëkalimin e duhur të rikthimeve. + Humbët kyçin tuaj të rimarrjeve\? Te rregullimet mund të caktoni një të ri. + S’u shfshehtëzua dot kopjeruajtja me këtë frazëkalim: ju lutemi, verifikoni që dhatë frazëkalimin e duhur të rimarrjeve. Gabim lidhjeje: ju lutemi, kontrolloni lidhjen tuaj dhe riprovoni. Po rikthehet kopjeruajtja: Shkyçeni Historikun - Ju lutemi, jepni një kyç rikthimesh - S’u shfshehtëzua dot kopjeruajtja me këtë kyç: ju lutemi, verifikoni që dhatë kyçin e duhur të rikthimeve. + Ju lutemi, jepni një kyç rimarrjesh + S’u shfshehtëzua dot kopjeruajtja me këtë kyç: ju lutemi, verifikoni që dhatë kyçin e duhur të rimarrjeve. Kopjeruajtja u Rikthye %s ! U rikthyen %1$d kyçe sesioni, dhe u shtuan %2$d kyç(e) të rinj që nuk njiheshin nga kjo pajisje @@ -1239,7 +1239,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani S’arrihet të fshihet kopjeruajtje (%s) Fshije Kopjeruajtjen - Të fshihen nga shërbyesi kyçet tuaj të kopjeruajtur të fshehtëzimit? S’do të jeni më në gjendje të përdorni kyçin tuaj të rikthimeve për lexim historiku mesazhesh të fshehtëzuar. + Të fshihen nga shërbyesi kyçet tuaj të kopjeruajtur të fshehtëzimit\? S’do të jeni më në gjendje të përdorni kyçin tuaj të rimarrjeve për lexim historiku mesazhesh të fshehtëzuar. Nëse dilni tani nga llogaria, do të humbni mesazhet tuaj të fshehtëzuar Kopjeruajtja e kyçeve po kryhet. Nëse dilni tani nga llogaria, do të humbni hyrjen te mesazhet tuaj të fshehtëzuar. @@ -1257,7 +1257,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Jeni i sigurt se doni të dilni? Mënyra e ruajtjes së të dhënave aplikon një filtër specifik, që kështu përditësimet rreth pranish dhe njoftime mbi shtypje në tastierë lihen jashtë. - Rikthim Mesazhesh të Fshehtëzuar + Rimarrje Mesazhesh të Fshehtëzuar Ju lutemi, jepni emër përdoruesi. Fillo të përdorësh Kopjeruajtje Kyçesh (Të mëtejshme) @@ -1268,13 +1268,13 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani \n \nPër maksimumin e sigurisë, ky duhet të jetë i ndryshëm nga fjalëkalimi juaj për llogarinë. Po Krijohet Kopjeruajtje - Ose, sigurojeni kopjeruajtjen tuaj me një Kyç Rikthimesh, duke e ruajtur këtë diku të parrezikuar. - (Të mëtejshme) Rregullojeni me një Kyç Rikthimesh + Ose, sigurojeni kopjeruajtjen tuaj me një Kyç Rimarrjesh, duke e ruajtur këtë diku të parrezikuar. + (Të mëtejshme) Rregullojeni me një Kyç Rimarrjesh Sukses! Kyçet tuaj po kopjeruhen. - Kyçi juaj i rikthimeve është një lloj rrjeti sigurie - mund ta përdorni për të rifituar hyrje te mesazhet tuaj të fshehtëzuar, nëse harroni frazëkalimin tuaj. -\nMbajeni kyçin tuaj të rikthimeve diku shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë) - Mbajeni kyçin tuaj të rikthimeve diku në një vend shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë) + Kyçi juaj i rimarrjeve është një lloj mase sigurie - mund ta përdorni për të rifituar hyrje te mesazhet tuaj të fshehtëzuar, nëse harroni frazëkalimin tuaj. +\nMbajeni kyçin tuaj të rimarrjeve diku shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë) + Mbajeni kyçin tuaj të rimarrjeve diku në një vend shumë të sigurt, bie fjala, nën një përgjegjës fjalëkalimesh (ose në një kasafortë) Bëra një kopje Ndajeni me të tjerë Mos humbni kurrë mesazhe të fshehtëzuar @@ -1310,14 +1310,14 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Fjalëkalimi s’është i vlefshëm Fjalëkalimet s’përputhen - Po përllogariten kyçe rikthimesh… + Po përllogariten kyçe rimarrjesh… Po shkarkohen kyçe… Po importohen kyçe… - Që të përdorni Kopjeruajtje Kyçesh në këtë pajisje, rikthejeni tani përmes frazëkalimit tuaj ose kyçit të rikthimeve. + Që të përdorni Kopjeruajtje Kyçesh në këtë pajisje, rikthejeni tani përmes frazëkalimit tuaj ose kyçit të rimarrjeve. Kopjeruajtje e Re Kyçesh - U pikas një kopjeruajtje e re kyçesh mesazhesh të sigurt. -\n -\nNëse metodën e re të rikthimeve nuk e caktuat ju, dikush mund të jetë duke u rrekur të hyjë në llogarinë tuaj. Ndryshoni menjëherë fjalëkalimin e llogarisë tuaj dhe caktoni një metodë të re rikthimesh, te Rregullimet. + U pikas një kopjeruajtje e re kyçesh mesazhesh të sigurt. +\n +\nNëse metodën e re të rimarrjeve nuk e caktuat ju, dikush mund të jetë duke u rrekur të hyjë në llogarinë tuaj. Ndryshoni menjëherë fjalëkalimin e llogarisë tuaj dhe caktoni një metodë të re rimarrjesh, te Rregullimet. Unë qeshë Përgjigje e pavlefshme zbulimi shërbyesi Home Mundësi Vetëplotësimi Shërbyesi @@ -1435,7 +1435,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Mirë se vini në vatër! Mbaroni punë me mesazhet e palexuara Biseda - Biseda përmes mesazhesh të drejtpërdrejta do të shfaqet këtu + Biseda përmes mesazhesh të drejtpërdrejta do të shfaqen këtu Dhoma Dhomat tuaja do të shfaqen këtu @@ -1601,7 +1601,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Caktoni një email për rimarrje llogarie, dhe më vonë të jetë i zbulueshëm (opsionale) nga persona që ju njohin. Caktoni një telefon, dhe më vonë të jetë i zbulueshëm (opsionale) nga persona që ju njohin. - Caktoni një email për rimarrje llogarie. Përdoni email ose telefon që të jeni i zbulueshëm (opsionale) nga persona që ju njohin. + Caktoni një email për rimarrje llogarie. Përdorni email ose telefon që të jeni i zbulueshëm (opsionale) nga persona që ju njohin. Caktoni një email për rimarrje llogarie. Përdorni email ose telefon që të jeni i zbulueshëm (opsionale) nga persona që ju njohin. S’kapet dot shërbyes Home te kjo URL, ju lutemi, kontrollojeni Do të përdoret %s si ndihmë kur shërbyesi juaj Home nuk ofron të tillë (gjatë thirrjes, adresa juaj IP do të ndahet me të tjerë) @@ -1663,7 +1663,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Ju lutemi, riprovoni sapo të keni pranuar termat dhe kushtet e shërbyesit tuaj Home. - Duket sikur shërbyesit po i duhet shumë kohë për t’u përgjigjur,kjo mund të shkaktohet ose nga lidhje e dobët, ose nga një gabim me shërbyesit tanë. Ju lutemi, riprovoni pas pak. + Duket sikur shërbyesit po i duhet shumë kohë për t’u përgjigjur,kjo mund të shkaktohet ose nga lidhje e dobët, ose nga një gabim me shërbyesin tonë. Ju lutemi, riprovoni pas pak. Dërgo bashkëngjitje @@ -1721,4 +1721,40 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Tani për tani s’la lidhje rrjeti + Ripohoni fjalëkalimin tuaj + Këtë s’e bëni dot që nga Riot-i për celular + Lypset mirëfilltësim + + + Integrime + Përdorni një Përgjegjës Integrimesh që të administroni robotë, ura, widget-e dhe paketa ngjitësish. +\nPërgjegjësit e Integrimeve marrin të dhëna formësimi dhe mund të ndryshojnë widget-e, të dërgojnë ftesa për në dhoma dhe të caktojnë shkallë pushteti në emrin tuaj. + Lejo integrim + Widget + Ngarko Widget + Ky widget qe shtuar nga: + Përdorimi i tij mund të sjellë depozitim cookies dhe ndarje të dhënash me %s: + Përdorimi i tij mund të sjellë ndarje të dhënash me %s: + S’u arrit të ngarkohej widget. +\n%s + Ringarkoje widget-in + Hape në shfletues + Shfuqizo hyrje për mua + + Emri juaj në ekran + URL-ja e avatarit tuaj + ID-ja juaj si përdorues + Tema juaj + ID Widget-i + ID Dhome + + + Ky widget dëshiron të përdorë burimet vijuese: + Lejoje + Bllokoji Krejt + Të përdorë kamerën + Të përdorë mikrofonin + Të lexojë Media të mbrojtur me DRM + + Mbyll banderolë kopjeruajtjeje kyçesh diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 0a4da5b085..ea1506ca99 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -1471,4 +1471,13 @@ Riot 在后台时的工作将被显著的限制,这可能会影响消息通知 RiotX - -下一代 Matrix 客户端 (已编辑) + + 撤消 + 断开连接 + 检查 + 拒绝 + + 没有设置身份服务器。 + + 服务器的错误配置导致通话失败 diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index a1d6ceb32b..577c1ef955 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1660,7 +1660,7 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 請在您接受您家伺服器的條款與條件前繼續重試。 - 看起來伺服器回應時間似乎太久了,這可能是不良的網路連線或我們的伺服器錯誤所造成。請稍後再試。 + 看起來伺服器回應時間似乎太久了,這可能是不良的網路連線或伺服器錯誤所造成。請稍後再試。 傳送附件 @@ -1720,4 +1720,41 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 目前沒有網路連線 + Latn + + 確認您的密碼 + 您無法在行動裝置上的 Riot 做這件事 + 需要驗證 + + + 整合 + 使用整合管理員以管理機器人、橋接、小工具與貼紙包。 +\n整合管理員可以代表您接收設定資料,調整小工具、傳送聊天室邀請並設定權力等級。 + 允許整合 + 小工具 + 載入小工具 + 此小工具新增由: + 使用它可能會設定 cookies 並與 %s 分享資料: + 使用它可能會與 %s 分享資料: + 載入小工具失敗。 +\n%s + 重新載入小工具 + 在瀏覽器中開啟 + 撤銷我的存取權限 + + 您的顯示名稱 + 您的大頭貼 URL + 您的使用者 ID + 您的佈景主題 + 小工具 ID + 聊天室 ID + + + 此小工具想要使用下列資源: + 允許 + 阻擋所有 + 使用相機 + 使用麥克風 + 讀取 DRM 保護的媒體 + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 2e4d04354b..8dd57df90a 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ en US - + Latn @@ -1801,4 +1801,166 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming There is no network connection right now + Block user + + "All messages (noisy)" + "All messages" + "Mentions only" + "Mute" + "Settings" + "Leave the room" + "%1$s made no changes" + Sends the given message as a spoiler + Spoiler + Type keywords to find a reaction. + + You are not ignoring any users + + Long click on a room to see more options + + + %1$s made the room public to whoever knows the link. + %1$s made the room invite only. + Unread messages + + Liberate your communication + Chat with people directly or in groups + Keep conversations private with encryption + Extend & customise your experience + Get started + + Select a server + Just like email, accounts have one home, although you can talk to anyone + Join millions free on the largest public server + Premium hosting for organisations + Learn more + Other + Custom & advanced settings + + Continue + + Connect to %1$s + Connect to Modular + Connect to a custom server + + Sign in to %1$s + Sign Up + Sign In + Continue with SSO + + Modular Address + Address + Premium hosting for organisations + Enter the address of the Modular Riot or Server you want to use + Enter the address of a server or a Riot you want to connect to + + An error occurred when loading the page: %1$s (%2$d) + The application is not able to signin to this homeserver. The homeserver supports the following signin type(s): %1$s.\n\nDo you want to signin using a web client? + Sorry, this server isn’t accepting new accounts. + The application is not able to create an account on this homeserver.\n\nDo you want to signup using a web client? + + This email is not associated to any account. + + + Reset password on %1$s + A verification email will be sent to your inbox to confirm setting your new password. + Next + Email + New password + + Warning! + Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password. + Continue + + This email is not linked to any account + + Check your inbox + + A verification email was sent to %1$s. + Tap on the link to confirm your new password. Once you\'ve followed the link it contains, click below. + I have verified my email address + + Success! + Your password has been reset. + You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device. + Back to Sign In + + Warning + Your password is not yet changed.\n\nStop the password change process? + + Set email address + Set an email to recover your account. Later, you can optionally allow people you know to discover you by your email. + Email + Email (optional) + Next + + Set phone number + Set a phone number to optionally allow people you know to discover you. + Please use the international format. + Phone number + Phone number (optional) + Next + + Confirm phone number + + We just sent a code to %1$s. Enter it below to verify it’s you. + Enter code + Send again + Next + + "International phone numbers must start with '+'" + "Phone number seems invalid. Please check it" + + + Sign up to %1$s + Username or email + Password + Next + That username is taken + Warning + Your account is not created yet.\n\nStop the registration process? + + Select matrix.org + Select modular + Select a custom homeserver + Please perform the captcha challenge + Accept terms to continue + + Please check your email + We just sent an email to %1$s.\nPlease click on the link it contains to continue the account creation. + The entered code is not correct. Please check. + Outdated homeserver + This homeserver is running too old a version to connect to. Ask your homeserver admin to upgrade. + + + Too many requests have been sent. You can retry in %1$d second… + Too many requests have been sent. You can retry in %1$d seconds… + + + Seen by + + You’re signed out + It can be due to various reasons:\n\n• You’ve changed your password on another device.\n\n• You have deleted this device from another device.\n\n• The administrator of your server has invalidated your access for security reason. + Sign in again + + You’re signed out + Sign in + + Your homeserver (%1$s) admin has signed you out of your account %2$s (%3$s). + Sign in to recover encryption keys stored exclusively on this device. You need them to read all of your secure messages on any device. + Sign in + Password + Clear personal data + Warning: Your personal data (including encryption keys) is still stored on this device.\n\nClear it if you’re finished using this device, or want to sign in to another account. + Clear all data + + Clear data + Clear all data currently stored on this device?\nSign in again to access your account data and messages. + You’ll lose access to secure messages unless you sign in to recover your encryption keys. + Clear data + The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by RiotX.\nPlease first clear data, then sign in again on another account. + + Your matrix.to link was malformed + The description is too short + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 753a15274a..71fbc22acf 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -2,165 +2,6 @@ - Block user - - "All messages (noisy)" - "All messages" - "Mentions only" - "Mute" - "Settings" - "Leave the room" - "%1$s made no changes" - Sends the given message as a spoiler - Spoiler - Type keywords to find a reaction. - - You are not ignoring any users - - Long click on a room to see more options - %1$s made the room public to whoever knows the link. - %1$s made the room invite only. - Unread messages - - Liberate your communication - Chat with people directly or in groups - Keep conversations private with encryption - Extend & customise your experience - Get started - - Select a server - Just like email, accounts have one home, although you can talk to anyone - Join millions free on the largest public server - Premium hosting for organisations - Learn more - Other - Custom & advanced settings - - Continue - - Connect to %1$s - Connect to Modular - Connect to a custom server - - Sign in to %1$s - Sign Up - Sign In - Continue with SSO - - Modular Address - Address - Premium hosting for organisations - Enter the address of the Modular Riot or Server you want to use - Enter the address of a server or a Riot you want to connect to - - An error occurred when loading the page: %1$s (%2$d) - The application is not able to signin to this homeserver. The homeserver supports the following signin type(s): %1$s.\n\nDo you want to signin using a web client? - Sorry, this server isn’t accepting new accounts. - The application is not able to create an account on this homeserver.\n\nDo you want to signup using a web client? - - This email is not associated to any account. - - - Reset password on %1$s - A verification email will be sent to your inbox to confirm setting your new password. - Next - Email - New password - - Warning! - Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password. - Continue - - This email is not linked to any account - - Check your inbox - - A verification email was sent to %1$s. - Tap on the link to confirm your new password. Once you\'ve followed the link it contains, click below. - I have verified my email address - - Success! - Your password has been reset. - You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device. - Back to Sign In - - Warning - Your password is not yet changed.\n\nStop the password change process? - - Set email address - Set an email to recover your account. Later, you can optionally allow people you know to discover you by your email. - Email - Email (optional) - Next - - Set phone number - Set a phone number to optionally allow people you know to discover you. - Please use the international format. - Phone number - Phone number (optional) - Next - - Confirm phone number - - We just sent a code to %1$s. Enter it below to verify it’s you. - Enter code - Send again - Next - - "International phone numbers must start with '+'" - "Phone number seems invalid. Please check it" - - - Sign up to %1$s - Username or email - Password - Next - That username is taken - Warning - Your account is not created yet.\n\nStop the registration process? - - Select matrix.org - Select modular - Select a custom homeserver - Please perform the captcha challenge - Accept terms to continue - - Please check your email - We just sent an email to %1$s.\nPlease click on the link it contains to continue the account creation. - The entered code is not correct. Please check. - Outdated homeserver - This homeserver is running too old a version to connect to. Ask your homeserver admin to upgrade. - - - Too many requests have been sent. You can retry in %1$d second… - Too many requests have been sent. You can retry in %1$d seconds… - - - Seen by - - You’re signed out - It can be due to various reasons:\n\n• You’ve changed your password on another device.\n\n• You have deleted this device from another device.\n\n• The administrator of your server has invalidated your access for security reason. - Sign in again - - You’re signed out - Sign in - - Your homeserver (%1$s) admin has signed you out of your account %2$s (%3$s). - Sign in to recover encryption keys stored exclusively on this device. You need them to read all of your secure messages on any device. - Sign in - Password - Clear personal data - Warning: Your personal data (including encryption keys) is still stored on this device.\n\nClear it if you’re finished using this device, or want to sign in to another account. - Clear all data - - Clear data - Clear all data currently stored on this device?\nSign in again to access your account data and messages. - You’ll lose access to secure messages unless you sign in to recover your encryption keys. - Clear data - The current session is for user %1$s and you provide credentials for user %2$s. This is not supported by RiotX.\nPlease first clear data, then sign in again on another account. - - Your matrix.to link was malformed - The description is too short From a6afd2e90402b3fa9676deb4f6ac2a23d8ba01fd Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2019 19:26:20 +0100 Subject: [PATCH 33/61] Timeline: handle failure when navigating to an unknown event (+ clean some files) --- .../session/room/timeline/TimelineTest.kt | 2 +- .../api/session/room/timeline/Timeline.kt | 9 +++++-- .../database/helper/ChunkEntityHelper.kt | 7 +----- .../session/room/RoomSummaryUpdater.kt | 6 ++--- .../session/room/timeline/DefaultTimeline.kt | 25 +++++++++++++++++-- .../room/timeline/TokenChunkEventPersistor.kt | 10 +++++--- .../android/internal/session/sync/SyncTask.kt | 1 - .../riotx/core/platform/VectorBaseFragment.kt | 8 ++++++ .../home/room/detail/RoomDetailFragment.kt | 10 ++++++++ .../home/room/detail/RoomDetailViewEvents.kt | 24 ++++++++++++++++++ .../home/room/detail/RoomDetailViewModel.kt | 14 ++++++++++- .../timeline/TimelineEventController.kt | 6 ++++- .../home/room/list/RoomListFragment.kt | 9 +------ 13 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt index 06651f9ba3..008508ae19 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/TimelineTest.kt @@ -66,7 +66,7 @@ internal class TimelineTest : InstrumentedTest { // val latch = CountDownLatch(2) // var timelineEvents: List = emptyList() // timeline.listener = object : Timeline.Listener { -// override fun onUpdated(snapshot: List) { +// override fun onTimelineUpdated(snapshot: List) { // if (snapshot.isNotEmpty()) { // if (initialLoad == 0) { // initialLoad = snapshot.size diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt index 85dbdcaa19..2280803e5c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/Timeline.kt @@ -65,7 +65,7 @@ interface Timeline { /** * This is the main method to enrich the timeline with new data. - * It will call the onUpdated method from [Listener] when the data will be processed. + * It will call the onTimelineUpdated method from [Listener] when the data will be processed. * It also ensures only one pagination by direction is launched at a time, so you can safely call this multiple time in a row. */ fun paginate(direction: Direction, count: Int) @@ -106,7 +106,12 @@ interface Timeline { * Call when the timeline has been updated through pagination or sync. * @param snapshot the most up to date snapshot */ - fun onUpdated(snapshot: List) + fun onTimelineUpdated(snapshot: List) + + /** + * Called whenever an error we can't recover from occurred + */ + fun onTimelineFailure(throwable: Throwable) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index 826b35254e..f05fa01444 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -21,12 +21,7 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.toEntity -import im.vector.matrix.android.internal.database.model.ChunkEntity -import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity -import im.vector.matrix.android.internal.database.model.ReadReceiptEntity -import im.vector.matrix.android.internal.database.model.ReadReceiptsSummaryEntity -import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.database.model.TimelineEventEntityFields +import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 126d13c5db..509beddbf0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity +import im.vector.matrix.android.internal.database.query.* import im.vector.matrix.android.internal.database.query.isEventRead import im.vector.matrix.android.internal.database.query.latestEvent import im.vector.matrix.android.internal.database.query.prev @@ -38,7 +39,6 @@ import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncUnreadNotifications import io.realm.Realm -import io.realm.kotlin.createObject import javax.inject.Inject internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId: String, @@ -69,9 +69,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId roomSummary: RoomSyncSummary? = null, unreadNotifications: RoomSyncUnreadNotifications? = null, updateMembers: Boolean = false) { - val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) - + val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) if (roomSummary != null) { if (roomSummary.heroes.isNotEmpty()) { roomSummaryEntity.heroes.clear() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index f42e50bdab..85bab5d706 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -638,7 +638,14 @@ internal class DefaultTimeline( private fun fetchEvent(eventId: String) { val params = GetContextOfEventTask.Params(roomId, eventId, settings.initialSize) - cancelableBag += contextOfEventTask.configureWith(params).executeBy(taskExecutor) + cancelableBag += contextOfEventTask.configureWith(params) { + callback = object : MatrixCallback { + override fun onFailure(failure: Throwable) { + postFailure(failure) + } + } + } + .executeBy(taskExecutor) } private fun postSnapshot() { @@ -651,7 +658,7 @@ internal class DefaultTimeline( val runnable = Runnable { synchronized(listeners) { listeners.forEach { - it.onUpdated(snapshot) + it.onTimelineUpdated(snapshot) } } } @@ -659,6 +666,20 @@ internal class DefaultTimeline( } } + private fun postFailure(throwable: Throwable) { + if (isReady.get().not()) { + return + } + val runnable = Runnable { + synchronized(listeners) { + listeners.forEach { + it.onTimelineFailure(throwable) + } + } + } + mainHandler.post(runnable) + } + private fun clearAllValues() { prevDisplayIndex = null nextDisplayIndex = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index 0d9fb4e9e6..7030509bfc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.helper.* +import im.vector.matrix.android.internal.database.helper.add +import im.vector.matrix.android.internal.database.helper.addOrUpdate +import im.vector.matrix.android.internal.database.helper.addStateEvent +import im.vector.matrix.android.internal.database.helper.deleteOnCascade import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.query.create @@ -112,7 +116,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) val nextToken: String? val prevToken: String? @@ -125,7 +129,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy } val shouldSkip = ChunkEntity.find(realm, roomId, nextToken = nextToken) != null - || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null + || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) val nextChunk = ChunkEntity.find(realm, roomId, prevToken = nextToken) @@ -139,7 +143,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy } else { nextChunk?.apply { this.prevToken = prevToken } } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) { Timber.v("Reach end of $roomId") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 189c80d657..d99b9df4df 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.internal.session.sync import im.vector.matrix.android.R -import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index efcbdfff39..902dfd33a7 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -31,6 +31,7 @@ import butterknife.Unbinder import com.airbnb.mvrx.BaseMvRxFragment import com.airbnb.mvrx.MvRx import com.bumptech.glide.util.Util.assertMainThread +import com.google.android.material.snackbar.Snackbar import im.vector.riotx.core.di.DaggerScreenComponent import im.vector.riotx.core.di.HasScreenInjector import im.vector.riotx.core.di.ScreenComponent @@ -167,6 +168,13 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { return this } + protected fun showErrorInSnackbar(throwable: Throwable) { + vectorBaseActivity.coordinatorLayout?.let { + Snackbar.make(it, errorFormatter.toHumanReadable(throwable), Snackbar.LENGTH_SHORT) + .show() + } + } + /* ========================================================================================== * Toolbar * ========================================================================================== */ diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 8887b94f92..64544a9035 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -274,6 +274,16 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.requestLiveData.observeEvent(this) { displayRoomDetailActionResult(it) } + + roomDetailViewModel.viewEvents + .observe() + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + when (it) { + is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) + } + } + .disposeOnDestroyView() } override fun onActivityCreated(savedInstanceState: Bundle?) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt new file mode 100644 index 0000000000..a1ad480584 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -0,0 +1,24 @@ +/* + * 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.riotx.features.home.room.detail + +/** + * Transient events for RoomDetail + */ +sealed class RoomDetailViewEvents { + data class Failure(val throwable: Throwable) : RoomDetailViewEvents() +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 614a75265d..467148302f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -27,6 +27,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isImageMessage @@ -56,7 +57,9 @@ import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.UserPreferencesProvider +import im.vector.riotx.core.utils.DataSource import im.vector.riotx.core.utils.LiveEvent +import im.vector.riotx.core.utils.PublishDataSource import im.vector.riotx.core.utils.subscribeLogError import im.vector.riotx.features.command.CommandParser import im.vector.riotx.features.command.ParsedCommand @@ -101,6 +104,9 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private var timelineEvents = PublishRelay.create>() private var timeline = room.createTimeline(eventId, timelineSettings) + private val _viewEvents = PublishDataSource() + val viewEvents: DataSource = _viewEvents + // Can be used for several actions, for a one shot result private val _requestLiveData = MutableLiveData>>() val requestLiveData: LiveData>> @@ -862,10 +868,16 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } - override fun onUpdated(snapshot: List) { + override fun onTimelineUpdated(snapshot: List) { timelineEvents.accept(snapshot) } + override fun onTimelineFailure(throwable: Throwable) { + // If we have a critical timeline issue, we get back to live. + timeline.restartWithEventId(null) + _viewEvents.post(RoomDetailViewEvents.Failure(throwable)) + } + override fun onCleared() { timeline.dispose() timeline.removeListener(this) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt index 0f187fa9e5..582544ce8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt @@ -236,10 +236,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // Timeline.LISTENER *************************************************************************** - override fun onUpdated(snapshot: List) { + override fun onTimelineUpdated(snapshot: List) { submitSnapshot(snapshot) } + override fun onTimelineFailure(throwable: Throwable) { + // no-op, already handled + } + private fun submitSnapshot(newSnapshot: List) { backgroundHandler.post { inSubmitList = true diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 9e54d5fc79..27fbc387b1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -104,7 +104,7 @@ class RoomListFragment @Inject constructor( .subscribe { when (it) { is RoomListViewEvents.SelectRoom -> openSelectedRoom(it) - is RoomListViewEvents.Failure -> showError(it) + is RoomListViewEvents.Failure -> showErrorInSnackbar(it.throwable) } } .disposeOnDestroyView() @@ -135,13 +135,6 @@ class RoomListFragment @Inject constructor( } } - private fun showError(event: RoomListViewEvents.Failure) { - vectorBaseActivity.coordinatorLayout?.let { - Snackbar.make(it, errorFormatter.toHumanReadable(event.throwable), Snackbar.LENGTH_SHORT) - .show() - } - } - private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.HOME -> createChatFabMenu.isVisible = true From 92315a4189a372f173dcf69dcd252609530cb0a0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 16:44:14 +0100 Subject: [PATCH 34/61] Prepare release 0.11.0 --- CHANGES.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index eef8808393..5143253a06 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -Changes in RiotX 0.11.0 (2019-XX-XX) +Changes in RiotX 0.11.0 (2019-12-19) =================================================== Features ✨: @@ -19,9 +19,6 @@ Bugfix 🐛: - Fix rendering issue with HTML formatted body - Disable click on Stickers (#703) -Translations 🗣: - - - Build 🧱: - Include diff-match-patch sources as dependency From 51d6b8828d9e6f191f3df14804d1ec439a5cda47 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 16:46:01 +0100 Subject: [PATCH 35/61] Version++ --- CHANGES.md | 21 +++++++++++++++++++++ vector/build.gradle | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 5143253a06..863b1ca455 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,24 @@ +Changes in RiotX 0.12.0 (2019-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Other changes: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +Build 🧱: + - + Changes in RiotX 0.11.0 (2019-12-19) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index de15a67fbd..c8d474088f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -15,7 +15,7 @@ androidExtensions { } ext.versionMajor = 0 -ext.versionMinor = 11 +ext.versionMinor = 12 ext.versionPatch = 0 static def getGitTimestamp() { From 0eb0870d6c7542ce493d18fa92159491a6fa06c8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 19 Dec 2019 18:29:46 +0100 Subject: [PATCH 36/61] AvatarRenderer: allow to pass GlideRequests too (fix home group avatar) --- .../java/im/vector/riotx/features/home/AvatarRenderer.kt | 8 ++++++++ .../im/vector/riotx/features/home/HomeDetailFragment.kt | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt index 4e1808a48a..b7c3e61ee4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt @@ -52,6 +52,14 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active DrawableImageViewTarget(imageView)) } + @UiThread + fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) { + render(imageView.context, + glideRequests, + matrixItem, + DrawableImageViewTarget(imageView)) + } + @UiThread fun render(context: Context, glideRequest: GlideRequests, diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index fc0eeaf92c..b9d3e3c95e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.extensions.commitTransactionNow +import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.ui.views.KeysBackupBanner @@ -75,7 +76,8 @@ class HomeDetailFragment @Inject constructor( private fun onGroupChange(groupSummary: GroupSummary?) { groupSummary?.let { - avatarRenderer.render(it.toMatrixItem(), groupToolbarAvatarImageView) + // Use GlideApp with activity context to avoid the glideRequests to be paused + avatarRenderer.render(it.toMatrixItem(), groupToolbarAvatarImageView, GlideApp.with(requireActivity())) } } From e73970d61b263eefba70c16e5ac9c56c95361504 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 19:39:35 +0100 Subject: [PATCH 37/61] Render aliases and canonical alias change in the timeline --- CHANGES.md | 2 +- .../src/main/res/values/strings_RiotX.xml | 15 +++++++++- .../action/MessageActionsViewModel.kt | 2 ++ .../timeline/factory/TimelineItemFactory.kt | 2 ++ .../timeline/format/NoticeEventFormatter.kt | 30 +++++++++++++++++++ .../helper/MessageInformationDataFactory.kt | 1 + .../helper/TimelineDisplayableEvents.kt | 2 ++ 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 863b1ca455..89c1942f39 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Render aliases and canonical alias change in the timeline Other changes: - diff --git a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml index 03bc6d3684..84aa9d9f5c 100644 --- a/matrix-sdk-android/src/main/res/values/strings_RiotX.xml +++ b/matrix-sdk-android/src/main/res/values/strings_RiotX.xml @@ -2,6 +2,19 @@ + + %1$s added %2$s as an address for this room. + %1$s added %2$s as addresses for this room. + + + %1$s removed %2$s as an address for this room. + %1$s removed %3$s as addresses for this room. + - \ No newline at end of file + %1$s added %2$s and removed %3$s as addresses for this room. + + "%1$s set the main address for this room to %2$s." + "%1$s removed the main address for this room." + + diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 1303c3aad9..be969fd532 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -187,6 +187,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_ALIASES, + EventType.STATE_CANONICAL_ALIAS, EventType.STATE_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 5b6dec9900..1c1155d7b2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -45,6 +45,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_ALIASES, + EventType.STATE_CANONICAL_ALIAS, EventType.STATE_ROOM_JOIN_RULES, EventType.STATE_HISTORY_VISIBILITY, EventType.CALL_INVITE, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 75100e6c03..2bf861a970 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -37,6 +37,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName()) EventType.CALL_INVITE, @@ -136,6 +138,34 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active } } + private fun formatRoomAliasesEvent(event: Event, senderName: String?): String? { + val eventContent: RoomAliasesContent? = event.getClearContent().toModel() + val prevEventContent: RoomAliasesContent? = event.unsignedData?.prevContent?.toModel() + + val addedAliases = eventContent?.aliases.orEmpty() - prevEventContent?.aliases.orEmpty() + val removedAliases = prevEventContent?.aliases.orEmpty() - eventContent?.aliases.orEmpty() + + return if (addedAliases.isNotEmpty() && removedAliases.isNotEmpty()) { + sp.getString(R.string.notice_room_aliases_added_and_removed, senderName, addedAliases.joinToString(), removedAliases.joinToString()) + } else if (addedAliases.isNotEmpty()) { + sp.getQuantityString(R.plurals.notice_room_aliases_added, addedAliases.size, senderName, addedAliases.joinToString()) + } else if (removedAliases.isNotEmpty()) { + sp.getQuantityString(R.plurals.notice_room_aliases_removed, removedAliases.size, senderName, removedAliases.joinToString()) + } else { + Timber.w("Alias event without any change...") + null + } + } + + private fun formatRoomCanonicalAliasEvent(event: Event, senderName: String?): String? { + val eventContent: RoomCanonicalAliasContent? = event.getClearContent().toModel() + val canonicalAlias = eventContent?.canonicalAlias + return canonicalAlias + ?.takeIf { it.isNotBlank() } + ?.let { sp.getString(R.string.notice_room_canonical_alias_set, senderName, it) } + ?: sp.getString(R.string.notice_room_canonical_alias_unset, senderName) + } + private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String { val displayText = StringBuilder() // Check display name has been changed diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 3331fbf774..7ee8486ba2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -33,6 +33,7 @@ import me.gujun.android.span.span import javax.inject.Inject /** + * TODO Update this comment * This class compute if data of an event (such has avatar, display name, ...) should be displayed, depending on the previous event in the timeline */ class MessageInformationDataFactory @Inject constructor(private val session: Session, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 1cd851f8c8..4bb0fc27d5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -27,6 +27,8 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_ALIASES, + EventType.STATE_CANONICAL_ALIAS, EventType.STATE_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, From 07817b69c24058ecdc775e2d6ad9fe3df49a7bbc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 19:46:09 +0100 Subject: [PATCH 38/61] Rename some event type --- .../api/session/events/model/EventType.kt | 14 +++---- .../room/model/RoomCanonicalAliasContent.kt | 2 +- .../room/model/create/CreateRoomParams.kt | 4 +- .../internal/crypto/DefaultCryptoService.kt | 12 +++--- .../session/room/RoomSummaryUpdater.kt | 4 +- .../membership/RoomDisplayNameResolver.kt | 2 +- .../session/room/prune/PruneEventTask.kt | 16 ++++---- .../action/MessageActionsViewModel.kt | 4 +- .../timeline/factory/TimelineItemFactory.kt | 4 +- .../timeline/format/NoticeEventFormatter.kt | 38 +++++++++---------- .../helper/TimelineDisplayableEvents.kt | 4 +- 11 files changed, 52 insertions(+), 52 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 38c24fa89b..bca2ded8bc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -50,10 +50,10 @@ object EventType { const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" const val STATE_ROOM_ALIASES = "m.room.aliases" const val STATE_ROOM_TOMBSTONE = "m.room.tombstone" - const val STATE_CANONICAL_ALIAS = "m.room.canonical_alias" - const val STATE_HISTORY_VISIBILITY = "m.room.history_visibility" - const val STATE_RELATED_GROUPS = "m.room.related_groups" - const val STATE_PINNED_EVENT = "m.room.pinned_events" + const val STATE_ROOM_CANONICAL_ALIAS = "m.room.canonical_alias" + const val STATE_ROOM_HISTORY_VISIBILITY = "m.room.history_visibility" + const val STATE_ROOM_RELATED_GROUPS = "m.room.related_groups" + const val STATE_ROOM_PINNED_EVENT = "m.room.pinned_events" // Call Events @@ -87,9 +87,9 @@ object EventType { STATE_ROOM_GUEST_ACCESS, STATE_ROOM_POWER_LEVELS, STATE_ROOM_TOMBSTONE, - STATE_HISTORY_VISIBILITY, - STATE_RELATED_GROUPS, - STATE_PINNED_EVENT + STATE_ROOM_HISTORY_VISIBILITY, + STATE_ROOM_RELATED_GROUPS, + STATE_ROOM_PINNED_EVENT ) fun isStateEvent(type: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt index a66f23555d..0aec7f6c8b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomCanonicalAliasContent.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass /** - * Class representing the EventType.STATE_CANONICAL_ALIAS state event content + * Class representing the EventType.STATE_ROOM_CANONICAL_ALIAS state event content */ @JsonClass(generateAdapter = true) data class RoomCanonicalAliasContent( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index 598aab2d30..bc1e941698 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -145,13 +145,13 @@ class CreateRoomParams { */ fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) { // Remove the existing value if any. - initialStates?.removeAll { it.getClearType() == EventType.STATE_HISTORY_VISIBILITY } + initialStates?.removeAll { it.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY } if (historyVisibility != null) { val contentMap = HashMap() contentMap["history_visibility"] = historyVisibility - val historyVisibilityEvent = Event(type = EventType.STATE_HISTORY_VISIBILITY, + val historyVisibilityEvent = Event(type = EventType.STATE_ROOM_HISTORY_VISIBILITY, stateKey = "", content = contentMap.toContent()) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index c50b9e2e10..28a9fad35f 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -145,17 +145,17 @@ internal class DefaultCryptoService @Inject constructor( fun onStateEvent(roomId: String, event: Event) { when { - event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event) - event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } } fun onLiveEvent(roomId: String, event: Event) { when { - event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event) - event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - event.getClearType() == EventType.STATE_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + event.getClearType() == EventType.ENCRYPTION -> onRoomEncryptionEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) + event.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 126d13c5db..c4d0d50283 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -52,7 +52,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, - EventType.STATE_HISTORY_VISIBILITY, + EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER, @@ -93,7 +93,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, filterTypes = PREVIEWABLE_TYPES) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev() - val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev() + val lastCanonicalAliasEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() val lastAliasesEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_ALIASES).prev() roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt index 2271631932..21270308ed 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -62,7 +62,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: return@doWithRealm } - val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_CANONICAL_ALIAS).prev() + val canonicalAlias = EventEntity.where(realm, roomId, EventType.STATE_ROOM_CANONICAL_ALIAS).prev() name = ContentMapper.map(canonicalAlias?.content).toModel()?.canonicalAlias if (!name.isNullOrEmpty()) { return@doWithRealm diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt index c303e1c215..de3eb1eab2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/prune/PruneEventTask.kt @@ -105,10 +105,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M private fun computeAllowedKeys(type: String): List { // Add filtered content, allowed keys in content depends on the event type return when (type) { - EventType.STATE_ROOM_MEMBER -> listOf("membership") - EventType.STATE_ROOM_CREATE -> listOf("creator") - EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") - EventType.STATE_ROOM_POWER_LEVELS -> listOf("users", + EventType.STATE_ROOM_MEMBER -> listOf("membership") + EventType.STATE_ROOM_CREATE -> listOf("creator") + EventType.STATE_ROOM_JOIN_RULES -> listOf("join_rule") + EventType.STATE_ROOM_POWER_LEVELS -> listOf("users", "users_default", "events", "events_default", @@ -117,10 +117,10 @@ internal class DefaultPruneEventTask @Inject constructor(private val monarchy: M "kick", "redact", "invite") - EventType.STATE_ROOM_ALIASES -> listOf("aliases") - EventType.STATE_CANONICAL_ALIAS -> listOf("alias") - EventType.FEEDBACK -> listOf("type", "target_event_id") - else -> emptyList() + EventType.STATE_ROOM_ALIASES -> listOf("aliases") + EventType.STATE_ROOM_CANONICAL_ALIAS -> listOf("alias") + EventType.FEEDBACK -> listOf("type", "target_event_id") + else -> emptyList() } } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index be969fd532..d537b66ec3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -188,8 +188,8 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, EventType.STATE_ROOM_ALIASES, - EventType.STATE_CANONICAL_ALIAS, - EventType.STATE_HISTORY_VISIBILITY, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 1c1155d7b2..4a7a1e2a86 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -46,9 +46,9 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, EventType.STATE_ROOM_ALIASES, - EventType.STATE_CANONICAL_ALIAS, + EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_JOIN_RULES, - EventType.STATE_HISTORY_VISIBILITY, + EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 2bf861a970..9cb045c01e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -33,21 +33,21 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { - EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) - EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName()) EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) + EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.MESSAGE, EventType.REACTION, - EventType.REDACTION -> formatDebug(timelineEvent.root) - else -> { + EventType.REDACTION -> formatDebug(timelineEvent.root) + else -> { Timber.v("Type $type not handled by this formatter") null } @@ -56,16 +56,16 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active fun format(event: Event, senderName: String?): CharSequence? { return when (val type = event.getClearType()) { - EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName) - EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName) - EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName) - EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName) - EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName) + EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName) + EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName) + EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName) + EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName) EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> formatCallEvent(event, senderName) - EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(senderName) - else -> { + EventType.CALL_ANSWER -> formatCallEvent(event, senderName) + EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(senderName) + else -> { Timber.v("Type $type not handled by this formatter") null } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 4bb0fc27d5..b0f3e617a6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -28,8 +28,8 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_MEMBER, EventType.STATE_ROOM_ALIASES, - EventType.STATE_CANONICAL_ALIAS, - EventType.STATE_HISTORY_VISIBILITY, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER, From d342356f29ea58300aebb815b1438663cc8e7e9c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 19:48:30 +0100 Subject: [PATCH 39/61] Add missing state events to the list (not sure about the side effects) --- .../vector/matrix/android/api/session/events/model/EventType.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index bca2ded8bc..22bf564a8a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -86,7 +86,9 @@ object EventType { STATE_ROOM_JOIN_RULES, STATE_ROOM_GUEST_ACCESS, STATE_ROOM_POWER_LEVELS, + STATE_ROOM_ALIASES, STATE_ROOM_TOMBSTONE, + STATE_ROOM_CANONICAL_ALIAS, STATE_ROOM_HISTORY_VISIBILITY, STATE_ROOM_RELATED_GROUPS, STATE_ROOM_PINNED_EVENT From c18be94986f9e77930a96cce0eccf26649af4185 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 20:03:10 +0100 Subject: [PATCH 40/61] Fix lots of trouble with the completion popup (resize, change mode, etc.) --- .../autocomplete/EpoxyAutocompletePresenter.kt | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt index 227f1b2f9c..0862fc059f 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt @@ -19,18 +19,18 @@ package im.vector.riotx.features.autocomplete import android.content.Context import android.database.DataSetObserver import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController -import com.airbnb.epoxy.EpoxyRecyclerView import com.otaliastudios.autocomplete.AutocompletePresenter abstract class EpoxyAutocompletePresenter(context: Context) : AutocompletePresenter(context), AutocompleteClickListener { - private var recyclerView: EpoxyRecyclerView? = null - private var clicks: AutocompletePresenter.ClickProvider? = null + private var recyclerView: RecyclerView? = null + private var clicks: ClickProvider? = null private var observer: Observer? = null - override fun registerClickProvider(provider: AutocompletePresenter.ClickProvider) { + override fun registerClickProvider(provider: ClickProvider) { this.clicks = provider } @@ -39,8 +39,10 @@ abstract class EpoxyAutocompletePresenter(context: Context) : AutocompletePre } override fun getView(): ViewGroup? { - recyclerView = EpoxyRecyclerView(context).apply { - setController(providesController()) + recyclerView = RecyclerView(context).apply { + layoutManager = LinearLayoutManager(context) + setHasFixedSize(false) + adapter = providesController().adapter observer?.let { adapter?.registerAdapterDataObserver(it) } @@ -52,6 +54,7 @@ abstract class EpoxyAutocompletePresenter(context: Context) : AutocompletePre override fun onViewShown() {} override fun onViewHidden() { + recyclerView?.adapter = null recyclerView = null observer = null } From 237b22df5923376275c34018b4a0398e780be678 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 19 Dec 2019 20:31:36 +0100 Subject: [PATCH 41/61] Fix lots of trouble with the completion popup (resize, change mode, etc.) - next step --- .../EpoxyAutocompletePresenter.kt | 94 ------------------- .../command/AutocompleteCommandPresenter.kt | 15 ++- .../user/AutocompleteUserPresenter.kt | 15 ++- 3 files changed, 20 insertions(+), 104 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt deleted file mode 100644 index 0862fc059f..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/EpoxyAutocompletePresenter.kt +++ /dev/null @@ -1,94 +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.riotx.features.autocomplete - -import android.content.Context -import android.database.DataSetObserver -import android.view.ViewGroup -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.airbnb.epoxy.EpoxyController -import com.otaliastudios.autocomplete.AutocompletePresenter - -abstract class EpoxyAutocompletePresenter(context: Context) : AutocompletePresenter(context), AutocompleteClickListener { - - private var recyclerView: RecyclerView? = null - private var clicks: ClickProvider? = null - private var observer: Observer? = null - - override fun registerClickProvider(provider: ClickProvider) { - this.clicks = provider - } - - override fun registerDataSetObserver(observer: DataSetObserver) { - this.observer = Observer(observer) - } - - override fun getView(): ViewGroup? { - recyclerView = RecyclerView(context).apply { - layoutManager = LinearLayoutManager(context) - setHasFixedSize(false) - adapter = providesController().adapter - observer?.let { - adapter?.registerAdapterDataObserver(it) - } - itemAnimator = null - } - return recyclerView - } - - override fun onViewShown() {} - - override fun onViewHidden() { - recyclerView?.adapter = null - recyclerView = null - observer = null - } - - abstract fun providesController(): EpoxyController - - protected fun dispatchLayoutChange() { - observer?.onChanged() - } - - override fun onItemClick(t: T) { - clicks?.click(t) - } - - private class Observer internal constructor(private val root: DataSetObserver) : RecyclerView.AdapterDataObserver() { - - override fun onChanged() { - root.onChanged() - } - - override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { - root.onChanged() - } - - override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { - root.onChanged() - } - - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - root.onChanged() - } - - override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { - root.onChanged() - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt index 915689fbeb..2f076c4c53 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt @@ -17,21 +17,26 @@ package im.vector.riotx.features.autocomplete.command import android.content.Context -import com.airbnb.epoxy.EpoxyController -import im.vector.riotx.features.autocomplete.EpoxyAutocompletePresenter +import androidx.recyclerview.widget.RecyclerView +import com.otaliastudios.autocomplete.RecyclerViewPresenter +import im.vector.riotx.features.autocomplete.AutocompleteClickListener import im.vector.riotx.features.command.Command import javax.inject.Inject class AutocompleteCommandPresenter @Inject constructor(context: Context, private val controller: AutocompleteCommandController) : - EpoxyAutocompletePresenter(context) { + RecyclerViewPresenter(context), AutocompleteClickListener { init { controller.listener = this } - override fun providesController(): EpoxyController { - return controller + override fun instantiateAdapter(): RecyclerView.Adapter<*> { + return controller.adapter + } + + override fun onItemClick(t: Command) { + dispatchClick(t) } override fun onQuery(query: CharSequence?) { diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt index 5c2d2c49c0..d60ea5db67 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt @@ -17,16 +17,17 @@ package im.vector.riotx.features.autocomplete.user import android.content.Context -import com.airbnb.epoxy.EpoxyController +import androidx.recyclerview.widget.RecyclerView import com.airbnb.mvrx.Async import com.airbnb.mvrx.Success +import com.otaliastudios.autocomplete.RecyclerViewPresenter import im.vector.matrix.android.api.session.user.model.User -import im.vector.riotx.features.autocomplete.EpoxyAutocompletePresenter +import im.vector.riotx.features.autocomplete.AutocompleteClickListener import javax.inject.Inject class AutocompleteUserPresenter @Inject constructor(context: Context, private val controller: AutocompleteUserController -) : EpoxyAutocompletePresenter(context) { +) : RecyclerViewPresenter(context), AutocompleteClickListener { var callback: Callback? = null @@ -34,8 +35,12 @@ class AutocompleteUserPresenter @Inject constructor(context: Context, controller.listener = this } - override fun providesController(): EpoxyController { - return controller + override fun instantiateAdapter(): RecyclerView.Adapter<*> { + return controller.adapter + } + + override fun onItemClick(t: User) { + dispatchClick(t) } override fun onQuery(query: CharSequence?) { From 3a829bdfe85d75bffd59fe6a4a10fb9b1f4d1c96 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 00:05:04 +0100 Subject: [PATCH 42/61] Fix command truncation --- vector/src/main/res/layout/item_autocomplete_command.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/item_autocomplete_command.xml b/vector/src/main/res/layout/item_autocomplete_command.xml index 0d858f0e02..4fe815fbed 100644 --- a/vector/src/main/res/layout/item_autocomplete_command.xml +++ b/vector/src/main/res/layout/item_autocomplete_command.xml @@ -41,7 +41,7 @@ android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_gravity="center_vertical" - android:maxLines="1" + android:maxLines="2" android:textColor="?riotx_text_secondary" android:textSize="12sp" tools:text="@string/command_description_invite_user" /> From 92f43a591acac58455cbb0f62f1e1d654cc70ad5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 00:38:42 +0100 Subject: [PATCH 43/61] Autocompletion for room canonical alias --- .../matrix/android/api/util/MatrixItem.kt | 8 ++- ...eUserItem.kt => AutocompleteMatrixItem.kt} | 14 +++-- .../room/AutocompleteRoomController.kt | 49 +++++++++++++++ .../room/AutocompleteRoomPresenter.kt | 59 +++++++++++++++++++ .../user/AutocompleteUserController.kt | 3 +- .../home/room/detail/RoomDetailFragment.kt | 58 ++++++++++++++++++ .../detail/composer/TextComposerAction.kt | 1 + .../detail/composer/TextComposerViewModel.kt | 38 ++++++++++-- .../detail/composer/TextComposerViewState.kt | 4 +- .../layout/item_autocomplete_matrix_item.xml | 51 ++++++++++++++++ .../res/layout/item_autocomplete_user.xml | 30 ---------- 11 files changed, 272 insertions(+), 43 deletions(-) rename vector/src/main/java/im/vector/riotx/features/autocomplete/{user/AutocompleteUserItem.kt => AutocompleteMatrixItem.kt} (71%) create mode 100644 vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt create mode 100644 vector/src/main/res/layout/item_autocomplete_matrix_item.xml delete mode 100644 vector/src/main/res/layout/item_autocomplete_user.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt index 4fed773ae2..1ca7237d3d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt @@ -62,6 +62,9 @@ sealed class MatrixItem( init { if (BuildConfig.DEBUG) checkId() } + + // Best name is the id, and we keep the displayName of the room for the case we need the first letter + override fun getBestName() = id } data class GroupItem(override val id: String, @@ -73,7 +76,7 @@ sealed class MatrixItem( } } - fun getBestName(): String { + open fun getBestName(): String { return displayName?.takeIf { it.isNotBlank() } ?: id } @@ -95,7 +98,7 @@ sealed class MatrixItem( } fun firstLetterOfDisplayName(): String { - return getBestName() + return (displayName?.takeIf { it.isNotBlank() } ?: id) .let { dn -> var startIndex = 0 val initial = dn[startIndex] @@ -138,4 +141,5 @@ sealed class MatrixItem( fun User.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun GroupSummary.toMatrixItem() = MatrixItem.GroupItem(groupId, displayName, avatarUrl) fun RoomSummary.toMatrixItem() = MatrixItem.RoomItem(roomId, displayName, avatarUrl) +fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl) fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name, avatarUrl) diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserItem.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/AutocompleteMatrixItem.kt similarity index 71% rename from vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserItem.kt rename to vector/src/main/java/im/vector/riotx/features/autocomplete/AutocompleteMatrixItem.kt index 8581ba8e2c..d5eb90a62c 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/AutocompleteMatrixItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.autocomplete.user +package im.vector.riotx.features.autocomplete import android.view.View import android.widget.ImageView @@ -25,23 +25,27 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.features.home.AvatarRenderer -@EpoxyModelClass(layout = R.layout.item_autocomplete_user) -abstract class AutocompleteUserItem : VectorEpoxyModel() { +@EpoxyModelClass(layout = R.layout.item_autocomplete_matrix_item) +abstract class AutocompleteMatrixItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var subName: String? = null @EpoxyAttribute var clickListener: View.OnClickListener? = null override fun bind(holder: Holder) { holder.view.setOnClickListener(clickListener) holder.nameView.text = matrixItem.getBestName() + holder.subNameView.setTextOrHide(subName) avatarRenderer.render(matrixItem, holder.avatarImageView) } class Holder : VectorEpoxyHolder() { - val nameView by bind(R.id.userAutocompleteName) - val avatarImageView by bind(R.id.userAutocompleteAvatar) + val nameView by bind(R.id.matrixItemAutocompleteName) + val subNameView by bind(R.id.matrixItemAutocompleteSubname) + val avatarImageView by bind(R.id.matrixItemAutocompleteAvatar) } } diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt new file mode 100644 index 0000000000..51285b02b7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomController.kt @@ -0,0 +1,49 @@ +/* + * 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.riotx.features.autocomplete.room + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import im.vector.riotx.features.autocomplete.autocompleteMatrixItem +import im.vector.riotx.features.home.AvatarRenderer +import javax.inject.Inject + +class AutocompleteRoomController @Inject constructor() : TypedEpoxyController>() { + + var listener: AutocompleteClickListener? = null + + @Inject lateinit var avatarRenderer: AvatarRenderer + + override fun buildModels(data: List?) { + if (data.isNullOrEmpty()) { + return + } + data.forEach { roomSummary -> + autocompleteMatrixItem { + id(roomSummary.roomId) + matrixItem(roomSummary.toMatrixItem()) + subName(roomSummary.canonicalAlias) + avatarRenderer(avatarRenderer) + clickListener { _ -> + listener?.onItemClick(roomSummary) + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt new file mode 100644 index 0000000000..e2b4f38e19 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt @@ -0,0 +1,59 @@ +/* + * 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.riotx.features.autocomplete.room + +import android.content.Context +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Success +import com.otaliastudios.autocomplete.RecyclerViewPresenter +import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import javax.inject.Inject + +class AutocompleteRoomPresenter @Inject constructor(context: Context, + private val controller: AutocompleteRoomController +) : RecyclerViewPresenter(context), AutocompleteClickListener { + + var callback: Callback? = null + + init { + controller.listener = this + } + + override fun instantiateAdapter(): RecyclerView.Adapter<*> { + return controller.adapter + } + + override fun onItemClick(t: RoomSummary) { + dispatchClick(t) + } + + override fun onQuery(query: CharSequence?) { + callback?.onQueryRooms(query) + } + + fun render(rooms: Async>) { + if (rooms is Success) { + controller.setData(rooms()) + } + } + + interface Callback { + fun onQueryRooms(query: CharSequence?) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt index 8f0090001f..53a87fe27a 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserController.kt @@ -20,6 +20,7 @@ import com.airbnb.epoxy.TypedEpoxyController import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import im.vector.riotx.features.autocomplete.autocompleteMatrixItem import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject @@ -34,7 +35,7 @@ class AutocompleteUserController @Inject constructor() : TypedEpoxyController

  • - autocompleteUserItem { + autocompleteMatrixItem { id(user.userId) matrixItem(user.toMatrixItem()) avatarRenderer(avatarRenderer) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 49f23f7f2c..0d20ce851e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -60,6 +60,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.Timeline @@ -68,6 +69,7 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.matrix.android.api.util.toRoomAliasMatrixItem import im.vector.riotx.R import im.vector.riotx.core.dialogs.withColoredButton import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer @@ -83,6 +85,7 @@ import im.vector.riotx.features.attachments.AttachmentsHelper import im.vector.riotx.features.attachments.ContactAttachment import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy +import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotx.features.command.Command import im.vector.riotx.features.home.AvatarRenderer @@ -140,6 +143,7 @@ class RoomDetailFragment @Inject constructor( private val commandAutocompletePolicy: CommandAutocompletePolicy, private val autocompleteCommandPresenter: AutocompleteCommandPresenter, private val autocompleteUserPresenter: AutocompleteUserPresenter, + private val autocompleteRoomPresenter: AutocompleteRoomPresenter, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, val roomDetailViewModelFactory: RoomDetailViewModel.Factory, @@ -150,6 +154,7 @@ class RoomDetailFragment @Inject constructor( VectorBaseFragment(), TimelineEventController.Callback, AutocompleteUserPresenter.Callback, + AutocompleteRoomPresenter.Callback, VectorInviteView.Callback, JumpToReadMarkerView.Callback, AttachmentTypeSelectorView.Callback, @@ -582,6 +587,52 @@ class RoomDetailFragment @Inject constructor( }) .build() + autocompleteRoomPresenter.callback = this + Autocomplete.on(composerLayout.composerEditText) + .with(CharPolicy('#', true)) + .with(autocompleteRoomPresenter) + .with(elevation) + .with(backgroundDrawable) + .with(object : AutocompleteCallback { + override fun onPopupItemClicked(editable: Editable, item: RoomSummary): Boolean { + // Detect last '#' and remove it + var startIndex = editable.lastIndexOf("#") + if (startIndex == -1) { + startIndex = 0 + } + + // Detect next word separator + var endIndex = editable.indexOf(" ", startIndex) + if (endIndex == -1) { + endIndex = editable.length + } + + // Replace the word by its completion + val matrixItem = item.toRoomAliasMatrixItem() + val displayName = matrixItem.getBestName() + + // with a trailing space + editable.replace(startIndex, endIndex, "$displayName ") + + // Add the span + val span = PillImageSpan( + glideRequests, + avatarRenderer, + requireContext(), + matrixItem + ) + span.bind(composerLayout.composerEditText) + + editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + + return true + } + + override fun onPopupVisibilityChanged(shown: Boolean) { + } + }) + .build() + autocompleteUserPresenter.callback = this Autocomplete.on(composerLayout.composerEditText) .with(CharPolicy('@', true)) @@ -724,6 +775,7 @@ class RoomDetailFragment @Inject constructor( private fun renderTextComposerState(state: TextComposerViewState) { autocompleteUserPresenter.render(state.asyncUsers) + autocompleteRoomPresenter.render(state.asyncRooms) } private fun renderTombstoneEventHandling(async: Async) { @@ -1056,6 +1108,12 @@ class RoomDetailFragment @Inject constructor( textComposerViewModel.handle(TextComposerAction.QueryUsers(query)) } + // AutocompleteRoomPresenter.Callback + + override fun onQueryRooms(query: CharSequence?) { + textComposerViewModel.handle(TextComposerAction.QueryRooms(query)) + } + private fun handleActions(action: EventSharedAction) { when (action) { is EventSharedAction.AddReaction -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt index 5d60fa1cef..9f94b086a2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt @@ -20,4 +20,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction sealed class TextComposerAction : VectorViewModelAction { data class QueryUsers(val query: CharSequence?) : TextComposerAction() + data class QueryRooms(val query: CharSequence?) : TextComposerAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt index 88548e12b4..c69ab7c5da 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt @@ -24,6 +24,7 @@ import com.jakewharton.rxrelay2.BehaviorRelay import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.VectorViewModel @@ -32,16 +33,16 @@ import io.reactivex.Observable import io.reactivex.functions.BiFunction import java.util.concurrent.TimeUnit -typealias AutocompleteUserQuery = CharSequence +typealias AutocompleteQuery = CharSequence class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: TextComposerViewState, private val session: Session ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! - private val roomId = initialState.roomId - private val usersQueryObservable = BehaviorRelay.create>() + private val usersQueryObservable = BehaviorRelay.create>() + private val roomsQueryObservable = BehaviorRelay.create>() @AssistedInject.Factory interface Factory { @@ -59,11 +60,13 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: init { observeUsersQuery() + observeRoomsQuery() } override fun handle(action: TextComposerAction) { when (action) { is TextComposerAction.QueryUsers -> handleQueryUsers(action) + is TextComposerAction.QueryRooms -> handleQueryRooms(action) } } @@ -72,8 +75,14 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: usersQueryObservable.accept(query) } + + private fun handleQueryRooms(action: TextComposerAction.QueryRooms) { + val query = Option.fromNullable(action.query) + roomsQueryObservable.accept(query) + } + private fun observeUsersQuery() { - Observable.combineLatest, Option, List>( + Observable.combineLatest, Option, List>( room.rx().liveRoomMemberIds(), usersQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS), BiFunction { roomMemberIds, query -> @@ -87,6 +96,7 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: it.displayName?.startsWith(prefix = filter, ignoreCase = true) ?: false } } + .sortedBy { it.displayName } } ).execute { async -> copy( @@ -94,4 +104,24 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: ) } } + + private fun observeRoomsQuery() { + Observable.combineLatest, Option, List>( + session.rx().liveRoomSummaries(), + roomsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS), + BiFunction { roomSummaries, query -> + val filter = query.orNull() ?: "" + // Keep only room with a canonical alias + roomSummaries + .filter { + it.canonicalAlias?.contains(filter, ignoreCase = true) == true + } + .sortedBy { it.displayName } + } + ).execute { async -> + copy( + asyncRooms = async + ) + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt index b2cec09096..671a7ac556 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt @@ -19,11 +19,13 @@ package im.vector.riotx.features.home.room.detail.composer import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.features.home.room.detail.RoomDetailArgs data class TextComposerViewState(val roomId: String, - val asyncUsers: Async> = Uninitialized + val asyncUsers: Async> = Uninitialized, + val asyncRooms: Async> = Uninitialized ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/res/layout/item_autocomplete_matrix_item.xml b/vector/src/main/res/layout/item_autocomplete_matrix_item.xml new file mode 100644 index 0000000000..9696a08bb5 --- /dev/null +++ b/vector/src/main/res/layout/item_autocomplete_matrix_item.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_autocomplete_user.xml b/vector/src/main/res/layout/item_autocomplete_user.xml deleted file mode 100644 index f2fdb354a9..0000000000 --- a/vector/src/main/res/layout/item_autocomplete_user.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - \ No newline at end of file From c31b64771bfa1692b02b558eaea99667e4bd7da3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 00:42:19 +0100 Subject: [PATCH 44/61] Autocompletion: disable animation on the recycler view items --- .../autocomplete/command/AutocompleteCommandPresenter.kt | 2 ++ .../features/autocomplete/room/AutocompleteRoomPresenter.kt | 2 ++ .../features/autocomplete/user/AutocompleteUserPresenter.kt | 2 ++ 3 files changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt index 2f076c4c53..84ae8db217 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/command/AutocompleteCommandPresenter.kt @@ -32,6 +32,8 @@ class AutocompleteCommandPresenter @Inject constructor(context: Context, } override fun instantiateAdapter(): RecyclerView.Adapter<*> { + // Also remove animation + recyclerView?.itemAnimator = null return controller.adapter } diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt index e2b4f38e19..53fed7f859 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/room/AutocompleteRoomPresenter.kt @@ -36,6 +36,8 @@ class AutocompleteRoomPresenter @Inject constructor(context: Context, } override fun instantiateAdapter(): RecyclerView.Adapter<*> { + // Also remove animation + recyclerView?.itemAnimator = null return controller.adapter } diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt index d60ea5db67..01dceb5399 100644 --- a/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/user/AutocompleteUserPresenter.kt @@ -36,6 +36,8 @@ class AutocompleteUserPresenter @Inject constructor(context: Context, } override fun instantiateAdapter(): RecyclerView.Adapter<*> { + // Also remove animation + recyclerView?.itemAnimator = null return controller.adapter } From 05a788453f44b84461586842cbdfc498698240ac Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 00:47:47 +0100 Subject: [PATCH 45/61] More generic name --- .../api/session/room/model/relation/RelationService.kt | 2 +- .../room/send/{UserMentionSpan.kt => MatrixItemSpan.kt} | 4 ++-- .../matrix/android/api/session/room/send/SendService.kt | 2 +- .../internal/session/room/send/pills/MentionLinkSpec.kt | 4 ++-- .../internal/session/room/send/pills/TextPillsUtils.kt | 6 +++--- .../java/im/vector/riotx/features/html/PillImageSpan.kt | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/{UserMentionSpan.kt => MatrixItemSpan.kt} (90%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index b3dd1c6f22..7d8f2f0bc1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -98,7 +98,7 @@ interface RelationService { /** * Reply to an event in the timeline (must be in same room) * https://matrix.org/docs/spec/client_server/r0.4.0.html#id350 - * The replyText can be a Spannable and contains special spans (UserMentionSpan) that will be translated + * The replyText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated * by the sdk into pills. * @param eventReplied the event referenced by the reply * @param replyText the reply text diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/UserMentionSpan.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/MatrixItemSpan.kt similarity index 90% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/UserMentionSpan.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/MatrixItemSpan.kt index 71a422bac8..d191f5197b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/UserMentionSpan.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/MatrixItemSpan.kt @@ -19,9 +19,9 @@ package im.vector.matrix.android.api.session.room.send import im.vector.matrix.android.api.util.MatrixItem /** - * Tag class for spans that should mention a user. + * Tag class for spans that should mention a matrix item. * These Spans will be transformed into pills when detected in message to send */ -interface UserMentionSpan { +interface MatrixItemSpan { val matrixItem: MatrixItem } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt index bdae5eaaa6..ac1b50bbcb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/SendService.kt @@ -29,7 +29,7 @@ interface SendService { /** * Method to send a text message asynchronously. - * The text to send can be a Spannable and contains special spans (UserMentionSpan) that will be translated + * The text to send can be a Spannable and contains special spans (MatrixItemSpan) that will be translated * by the sdk into pills. * @param text the text message to send * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/MentionLinkSpec.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/MentionLinkSpec.kt index 5ad61b5441..055eade5e7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/MentionLinkSpec.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/MentionLinkSpec.kt @@ -16,10 +16,10 @@ package im.vector.matrix.android.internal.session.room.send.pills -import im.vector.matrix.android.api.session.room.send.UserMentionSpan +import im.vector.matrix.android.api.session.room.send.MatrixItemSpan internal data class MentionLinkSpec( - val span: UserMentionSpan, + val span: MatrixItemSpan, val start: Int, val end: Int ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt index c079e456c0..b512f27602 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt @@ -16,7 +16,7 @@ package im.vector.matrix.android.internal.session.room.send.pills import android.text.SpannableString -import im.vector.matrix.android.api.session.room.send.UserMentionSpan +import im.vector.matrix.android.api.session.room.send.MatrixItemSpan import java.util.* import javax.inject.Inject @@ -49,7 +49,7 @@ internal class TextPillsUtils @Inject constructor( private fun transformPills(text: CharSequence, template: String): String? { val spannableString = SpannableString.valueOf(text) val pills = spannableString - ?.getSpans(0, text.length, UserMentionSpan::class.java) + ?.getSpans(0, text.length, MatrixItemSpan::class.java) ?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) } ?.toMutableList() ?.takeIf { it.isNotEmpty() } @@ -65,7 +65,7 @@ internal class TextPillsUtils @Inject constructor( // append text before pill append(text, currIndex, start) // append the pill - append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.displayName)) + append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.getBestName())) currIndex = end } // append text after the last pill diff --git a/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt index 8b57006439..a609541a62 100644 --- a/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt +++ b/vector/src/main/java/im/vector/riotx/features/html/PillImageSpan.kt @@ -28,7 +28,7 @@ import androidx.annotation.UiThread import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.google.android.material.chip.ChipDrawable -import im.vector.matrix.android.api.session.room.send.UserMentionSpan +import im.vector.matrix.android.api.session.room.send.MatrixItemSpan import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.glide.GlideRequests @@ -38,13 +38,13 @@ import java.lang.ref.WeakReference /** * This span is able to replace a text by a [ChipDrawable] * It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached. - * Implements UserMentionSpan so that it could be automatically transformed in matrix links and displayed as pills. + * Implements MatrixItemSpan so that it could be automatically transformed in matrix links and displayed as pills. */ class PillImageSpan(private val glideRequests: GlideRequests, private val avatarRenderer: AvatarRenderer, private val context: Context, override val matrixItem: MatrixItem -) : ReplacementSpan(), UserMentionSpan { +) : ReplacementSpan(), MatrixItemSpan { private val pillDrawable = createChipDrawable() private val target = PillImageSpanTarget(this) From 543c07fd695adc295168618d4784011caa09b7da Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 01:23:45 +0100 Subject: [PATCH 46/61] Render pills for room links --- .../android/api/session/room/RoomService.kt | 7 +++ .../session/room/DefaultRoomService.kt | 17 ++++++ .../riotx/features/html/MxLinkTagHandler.kt | 53 ++++++++++++------- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index afe7cf8bc3..ba3b5ded78 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -52,6 +52,13 @@ interface RoomService { */ fun getRoom(roomId: String): Room? + /** + * Get a roomSummary from a roomId or a room alias + * @param roomIdOrAlias the roomId or the alias of a room to look for. + * @return a matching room summary or null + */ + fun getRoomSummary(roomIdOrAlias: String): RoomSummary? + /** * Get a live list of room summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of [RoomSummary] diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index de60e6e7e4..b53fa3ce33 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields +import im.vector.matrix.android.internal.database.query.findByAlias import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask import im.vector.matrix.android.internal.session.room.create.CreateRoomTask @@ -38,6 +39,7 @@ import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateBreadcrumbsTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.fetchCopyMap import io.realm.Realm import javax.inject.Inject @@ -69,6 +71,21 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona } } + override fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { + return monarchy + .fetchCopyMap({ + if (roomIdOrAlias.startsWith("!")) { + // It's a roomId + RoomSummaryEntity.where(it, roomId = roomIdOrAlias).findFirst() + } else { + // Assume it's a room alias + RoomSummaryEntity.findByAlias(it, roomIdOrAlias) + } + }, { entity, _ -> + roomSummaryMapper.map(entity) + }) + } + override fun liveRoomSummaries(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> diff --git a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt index 3f16666221..7e5a82060a 100644 --- a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt @@ -20,6 +20,7 @@ import android.content.Context import android.text.style.URLSpan import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkParser +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.glide.GlideRequests @@ -39,26 +40,42 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests, val link = tag.attributes()["href"] if (link != null) { val permalinkData = PermalinkParser.parse(link) - when (permalinkData) { - is PermalinkData.UserLink -> { + val matrixItem = when (permalinkData) { + is PermalinkData.UserLink -> { val user = sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId) - val span = PillImageSpan(glideRequests, avatarRenderer, context, MatrixItem.UserItem(permalinkData.userId, user?.displayName - ?: permalinkData.userId, user?.avatarUrl)) - SpannableBuilder.setSpans( - visitor.builder(), - span, - tag.start(), - tag.end() - ) - // also add clickable span - SpannableBuilder.setSpans( - visitor.builder(), - URLSpan(link), - tag.start(), - tag.end() - ) + MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl) } - else -> super.handle(visitor, renderer, tag) + is PermalinkData.RoomLink -> { + 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) + } + } + is PermalinkData.GroupLink -> { + // TODO val group = sessionHolder.getSafeActiveSession()?.getGroup(permalinkData.groupId) + MatrixItem.RoomItem(permalinkData.groupId /* TODO Group display name and avatar */) + } + 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) From 8dce98c53827187d1bc13542b1609c652e50e2a1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 02:15:48 +0100 Subject: [PATCH 47/61] Autocompletion: group (including pills for groups) --- .../android/api/session/group/GroupService.kt | 7 +++ .../matrix/android/api/util/MatrixItem.kt | 3 + .../session/group/DefaultGroupService.kt | 8 +++ .../session/room/send/pills/TextPillsUtils.kt | 2 - .../group/AutocompleteGroupPresenter.kt | 61 +++++++++++++++++++ .../group/AutocompleteRoomController.kt | 48 +++++++++++++++ .../home/room/detail/RoomDetailFragment.kt | 57 +++++++++++++++++ .../detail/composer/TextComposerAction.kt | 1 + .../detail/composer/TextComposerViewModel.kt | 32 +++++++++- .../detail/composer/TextComposerViewState.kt | 4 +- .../riotx/features/html/MxLinkTagHandler.kt | 4 +- 11 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteRoomController.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt index ff63d1a9e7..2d55d0be57 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/group/GroupService.kt @@ -31,6 +31,13 @@ interface GroupService { */ fun getGroup(groupId: String): Group? + /** + * Get a groupSummary from a groupId + * @param groupId the groupId to look for. + * @return the groupSummary with groupId or null + */ + fun getGroupSummary(groupId: String): GroupSummary? + /** * Get a live list of group summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of [GroupSummary] diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt index 1ca7237d3d..4c8082b77e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/MatrixItem.kt @@ -74,6 +74,9 @@ sealed class MatrixItem( init { if (BuildConfig.DEBUG) checkId() } + + // Best name is the id, and we keep the displayName of the room for the case we need the first letter + override fun getBestName() = id } open fun getBestName(): String { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt index be059038f3..192c6fe40c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGroupService.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntityFields import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.util.fetchCopyMap import javax.inject.Inject internal class DefaultGroupService @Inject constructor(private val monarchy: Monarchy) : GroupService { @@ -33,6 +34,13 @@ internal class DefaultGroupService @Inject constructor(private val monarchy: Mon return null } + override fun getGroupSummary(groupId: String): GroupSummary? { + return monarchy.fetchCopyMap( + { realm -> GroupSummaryEntity.where(realm, groupId).findFirst() }, + { it, _ -> it.asDomain() } + ) + } + override fun liveGroupSummaries(): LiveData> { return monarchy.findAllMappedWithChanges( { realm -> GroupSummaryEntity.where(realm).isNotEmpty(GroupSummaryEntityFields.DISPLAY_NAME) }, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt index b512f27602..1a7b8228b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt @@ -23,8 +23,6 @@ import javax.inject.Inject /** * Utility class to detect special span in CharSequence and turn them into * formatted text to send them as a Matrix messages. - * - * For now only support UserMentionSpans (TODO rooms, room aliases, etc...) */ internal class TextPillsUtils @Inject constructor( private val mentionLinkSpecComparator: MentionLinkSpecComparator diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.kt new file mode 100644 index 0000000000..822ce451e7 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupPresenter.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.riotx.features.autocomplete.group + +import android.content.Context +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Success +import com.otaliastudios.autocomplete.RecyclerViewPresenter +import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import javax.inject.Inject + +class AutocompleteGroupPresenter @Inject constructor(context: Context, + private val controller: AutocompleteGroupController +) : RecyclerViewPresenter(context), AutocompleteClickListener { + + var callback: Callback? = null + + init { + controller.listener = this + } + + override fun instantiateAdapter(): RecyclerView.Adapter<*> { + // Also remove animation + recyclerView?.itemAnimator = null + return controller.adapter + } + + override fun onItemClick(t: GroupSummary) { + dispatchClick(t) + } + + override fun onQuery(query: CharSequence?) { + callback?.onQueryGroups(query) + } + + fun render(groups: Async>) { + if (groups is Success) { + controller.setData(groups()) + } + } + + interface Callback { + fun onQueryGroups(query: CharSequence?) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteRoomController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteRoomController.kt new file mode 100644 index 0000000000..5d0d43d9ea --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteRoomController.kt @@ -0,0 +1,48 @@ +/* + * 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.riotx.features.autocomplete.group + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.util.toMatrixItem +import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import im.vector.riotx.features.autocomplete.autocompleteMatrixItem +import im.vector.riotx.features.home.AvatarRenderer +import javax.inject.Inject + +class AutocompleteGroupController @Inject constructor() : TypedEpoxyController>() { + + var listener: AutocompleteClickListener? = null + + @Inject lateinit var avatarRenderer: AvatarRenderer + + override fun buildModels(data: List?) { + if (data.isNullOrEmpty()) { + return + } + data.forEach { groupSummary -> + autocompleteMatrixItem { + id(groupSummary.groupId) + matrixItem(groupSummary.toMatrixItem()) + avatarRenderer(avatarRenderer) + clickListener { _ -> + listener?.onItemClick(groupSummary) + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 0d20ce851e..cb54aba651 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -59,6 +59,7 @@ import im.vector.matrix.android.api.permalinks.PermalinkFactory import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.message.* @@ -85,6 +86,7 @@ import im.vector.riotx.features.attachments.AttachmentsHelper import im.vector.riotx.features.attachments.ContactAttachment import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy +import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter import im.vector.riotx.features.command.Command @@ -144,6 +146,7 @@ class RoomDetailFragment @Inject constructor( private val autocompleteCommandPresenter: AutocompleteCommandPresenter, private val autocompleteUserPresenter: AutocompleteUserPresenter, private val autocompleteRoomPresenter: AutocompleteRoomPresenter, + private val autocompleteGroupPresenter: AutocompleteGroupPresenter, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, val roomDetailViewModelFactory: RoomDetailViewModel.Factory, @@ -155,6 +158,7 @@ class RoomDetailFragment @Inject constructor( TimelineEventController.Callback, AutocompleteUserPresenter.Callback, AutocompleteRoomPresenter.Callback, + AutocompleteGroupPresenter.Callback, VectorInviteView.Callback, JumpToReadMarkerView.Callback, AttachmentTypeSelectorView.Callback, @@ -633,6 +637,52 @@ class RoomDetailFragment @Inject constructor( }) .build() + autocompleteGroupPresenter.callback = this + Autocomplete.on(composerLayout.composerEditText) + .with(CharPolicy('+', true)) + .with(autocompleteGroupPresenter) + .with(elevation) + .with(backgroundDrawable) + .with(object : AutocompleteCallback { + override fun onPopupItemClicked(editable: Editable, item: GroupSummary): Boolean { + // Detect last '+' and remove it + var startIndex = editable.lastIndexOf("+") + if (startIndex == -1) { + startIndex = 0 + } + + // Detect next word separator + var endIndex = editable.indexOf(" ", startIndex) + if (endIndex == -1) { + endIndex = editable.length + } + + // Replace the word by its completion + val matrixItem = item.toMatrixItem() + val displayName = matrixItem.getBestName() + + // with a trailing space + editable.replace(startIndex, endIndex, "$displayName ") + + // Add the span + val span = PillImageSpan( + glideRequests, + avatarRenderer, + requireContext(), + matrixItem + ) + span.bind(composerLayout.composerEditText) + + editable.setSpan(span, startIndex, startIndex + displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + + return true + } + + override fun onPopupVisibilityChanged(shown: Boolean) { + } + }) + .build() + autocompleteUserPresenter.callback = this Autocomplete.on(composerLayout.composerEditText) .with(CharPolicy('@', true)) @@ -776,6 +826,7 @@ class RoomDetailFragment @Inject constructor( private fun renderTextComposerState(state: TextComposerViewState) { autocompleteUserPresenter.render(state.asyncUsers) autocompleteRoomPresenter.render(state.asyncRooms) + autocompleteGroupPresenter.render(state.asyncGroups) } private fun renderTombstoneEventHandling(async: Async) { @@ -1114,6 +1165,12 @@ class RoomDetailFragment @Inject constructor( textComposerViewModel.handle(TextComposerAction.QueryRooms(query)) } + // AutocompleteGroupPresenter.Callback + + override fun onQueryGroups(query: CharSequence?) { + textComposerViewModel.handle(TextComposerAction.QueryGroups(query)) + } + private fun handleActions(action: EventSharedAction) { when (action) { is EventSharedAction.AddReaction -> { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt index 9f94b086a2..0f5bf2a8c5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerAction.kt @@ -21,4 +21,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction sealed class TextComposerAction : VectorViewModelAction { data class QueryUsers(val query: CharSequence?) : TextComposerAction() data class QueryRooms(val query: CharSequence?) : TextComposerAction() + data class QueryGroups(val query: CharSequence?) : TextComposerAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt index c69ab7c5da..9d6d9aab2e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt @@ -24,6 +24,7 @@ import com.jakewharton.rxrelay2.BehaviorRelay import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx @@ -43,6 +44,7 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: private val usersQueryObservable = BehaviorRelay.create>() private val roomsQueryObservable = BehaviorRelay.create>() + private val groupsQueryObservable = BehaviorRelay.create>() @AssistedInject.Factory interface Factory { @@ -61,12 +63,14 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: init { observeUsersQuery() observeRoomsQuery() + observeGroupsQuery() } override fun handle(action: TextComposerAction) { when (action) { - is TextComposerAction.QueryUsers -> handleQueryUsers(action) - is TextComposerAction.QueryRooms -> handleQueryRooms(action) + is TextComposerAction.QueryUsers -> handleQueryUsers(action) + is TextComposerAction.QueryRooms -> handleQueryRooms(action) + is TextComposerAction.QueryGroups -> handleQueryGroups(action) } } @@ -81,6 +85,11 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: roomsQueryObservable.accept(query) } + private fun handleQueryGroups(action: TextComposerAction.QueryGroups) { + val query = Option.fromNullable(action.query) + groupsQueryObservable.accept(query) + } + private fun observeUsersQuery() { Observable.combineLatest, Option, List>( room.rx().liveRoomMemberIds(), @@ -124,4 +133,23 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: ) } } + + private fun observeGroupsQuery() { + Observable.combineLatest, Option, List>( + session.rx().liveGroupSummaries(), + groupsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS), + BiFunction { groupSummaries, query -> + val filter = query.orNull() ?: "" + groupSummaries + .filter { + it.groupId.contains(filter, ignoreCase = true) + } + .sortedBy { it.displayName } + } + ).execute { async -> + copy( + asyncGroups = async + ) + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt index 671a7ac556..e863970afe 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewState.kt @@ -19,13 +19,15 @@ package im.vector.riotx.features.home.room.detail.composer import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.group.model.GroupSummary import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.features.home.room.detail.RoomDetailArgs data class TextComposerViewState(val roomId: String, val asyncUsers: Async> = Uninitialized, - val asyncRooms: Async> = Uninitialized + val asyncRooms: Async> = Uninitialized, + val asyncGroups: Async> = Uninitialized ) : MvRxState { constructor(args: RoomDetailArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt index 7e5a82060a..ff85a4d2cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt @@ -54,8 +54,8 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests, } } is PermalinkData.GroupLink -> { - // TODO val group = sessionHolder.getSafeActiveSession()?.getGroup(permalinkData.groupId) - MatrixItem.RoomItem(permalinkData.groupId /* TODO Group display name and avatar */) + val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId) + MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl) } else -> null } From c79b35b089bb8018acc2db569056bdbfe1bd9986 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 10:01:06 +0100 Subject: [PATCH 48/61] Autocomplete item layout --- vector/src/main/res/layout/item_autocomplete_command.xml | 9 ++++++--- .../main/res/layout/item_autocomplete_matrix_item.xml | 7 ++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/layout/item_autocomplete_command.xml b/vector/src/main/res/layout/item_autocomplete_command.xml index 4fe815fbed..ce2cd6c731 100644 --- a/vector/src/main/res/layout/item_autocomplete_command.xml +++ b/vector/src/main/res/layout/item_autocomplete_command.xml @@ -1,11 +1,14 @@ - + android:foreground="?attr/selectableItemBackground" + android:paddingStart="8dp" + android:paddingTop="6dp" + android:paddingEnd="8dp" + android:paddingBottom="6dp"> @@ -18,8 +19,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginStart="12dp" - android:layout_marginLeft="12dp" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" android:orientation="vertical"> Date: Fri, 20 Dec 2019 10:15:53 +0100 Subject: [PATCH 49/61] Remove extra blank line --- vector/src/main/res/layout/bottom_sheet_generic_list.xml | 1 - vector/src/main/res/layout/fragment_public_rooms.xml | 1 - vector/src/main/res/layout/fragment_room_directory_picker.xml | 1 - .../src/main/res/layout/item_timeline_event_code_block_stub.xml | 1 - .../main/res/layout/item_timeline_event_merged_header_stub.xml | 1 - vector/src/main/res/layout/vector_message_merge_avatar_list.xml | 1 - .../src/main/res/layout/vector_settings_address_preference.xml | 1 - .../res/layout/vector_settings_list_preference_with_warning.xml | 1 - vector/src/main/res/layout/vector_settings_round_avatar.xml | 1 - .../src/main/res/layout/vector_settings_spinner_preference.xml | 1 - vector/src/main/res/layout/view_attachment_type_selector.xml | 1 - vector/src/main/res/layout/view_read_marker.xml | 1 - 12 files changed, 12 deletions(-) diff --git a/vector/src/main/res/layout/bottom_sheet_generic_list.xml b/vector/src/main/res/layout/bottom_sheet_generic_list.xml index 69b5ce2fac..0bd6665325 100644 --- a/vector/src/main/res/layout/bottom_sheet_generic_list.xml +++ b/vector/src/main/res/layout/bottom_sheet_generic_list.xml @@ -1,5 +1,4 @@ - - - - - - - diff --git a/vector/src/main/res/layout/vector_settings_list_preference_with_warning.xml b/vector/src/main/res/layout/vector_settings_list_preference_with_warning.xml index 137d96408d..548594fcd4 100644 --- a/vector/src/main/res/layout/vector_settings_list_preference_with_warning.xml +++ b/vector/src/main/res/layout/vector_settings_list_preference_with_warning.xml @@ -1,5 +1,4 @@ - diff --git a/vector/src/main/res/layout/vector_settings_round_avatar.xml b/vector/src/main/res/layout/vector_settings_round_avatar.xml index 66c2d2412b..fba69dc588 100644 --- a/vector/src/main/res/layout/vector_settings_round_avatar.xml +++ b/vector/src/main/res/layout/vector_settings_round_avatar.xml @@ -1,5 +1,4 @@ - - diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index f713561084..134ad47c92 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -1,5 +1,4 @@ - - Date: Fri, 20 Dec 2019 10:16:17 +0100 Subject: [PATCH 50/61] Update changes.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 89c1942f39..ac4b27596e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Render aliases and canonical alias change in the timeline + - Fix autocompletion issues and add support for rooms and groups Other changes: - From c992d32afdfaf9c66d8815e2f2e264480eff1274 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 10:23:45 +0100 Subject: [PATCH 51/61] Improve algo --- .../detail/composer/TextComposerViewModel.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt index 9d6d9aab2e..538d13bb4e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt @@ -102,7 +102,7 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: users } else { users.filter { - it.displayName?.startsWith(prefix = filter, ignoreCase = true) ?: false + it.displayName?.contains(filter, ignoreCase = true) ?: false } } .sortedBy { it.displayName } @@ -139,11 +139,15 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: session.rx().liveGroupSummaries(), groupsQueryObservable.throttleLast(300, TimeUnit.MILLISECONDS), BiFunction { groupSummaries, query -> - val filter = query.orNull() ?: "" - groupSummaries - .filter { - it.groupId.contains(filter, ignoreCase = true) - } + val filter = query.orNull() + if (filter.isNullOrBlank()) { + groupSummaries + } else { + groupSummaries + .filter { + it.groupId.contains(filter, ignoreCase = true) + } + } .sortedBy { it.displayName } } ).execute { async -> From 3b0624ea401d6a4b1878804f78dd0f7a8298ca9b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 10:54:48 +0100 Subject: [PATCH 52/61] Fix issue with "in reply to" link --- .../vector/riotx/features/html/MxLinkTagHandler.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt index ff85a4d2cb..1167427a04 100644 --- a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt @@ -46,11 +46,16 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests, MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl) } is PermalinkData.RoomLink -> { - val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias) - if (permalinkData.isRoomAlias) { - MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl) + // Exclude event link (used in reply event) + 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 { - MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl) + null } } is PermalinkData.GroupLink -> { From 3ee5a7f54d48af4a23d5b7fedd8b69e67b8086b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 10:55:08 +0100 Subject: [PATCH 53/61] Better code --- .../android/api/permalinks/PermalinkParser.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt index d10152f4fe..871c30e46a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkParser.kt @@ -56,23 +56,23 @@ object PermalinkParser { val identifier = params.getOrNull(0) val extraParameter = params.getOrNull(1) - if (identifier.isNullOrEmpty()) { - return PermalinkData.FallbackLink(uri) - } return when { + identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) MatrixPatterns.isRoomId(identifier) -> { - val eventId = extraParameter.takeIf { - !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) - } - PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false, eventId = eventId) + PermalinkData.RoomLink( + roomIdOrAlias = identifier, + isRoomAlias = false, + eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) } + ) } MatrixPatterns.isRoomAlias(identifier) -> { - val eventId = extraParameter.takeIf { - !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) - } - PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true, eventId = eventId) + PermalinkData.RoomLink( + roomIdOrAlias = identifier, + isRoomAlias = true, + eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) } + ) } else -> PermalinkData.FallbackLink(uri) } From 54f2ac0d8cea552e71b4691a0089c37e9772d9b6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 10:59:41 +0100 Subject: [PATCH 54/61] Better comment --- .../main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt index 1167427a04..76f7dfaabd 100644 --- a/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/html/MxLinkTagHandler.kt @@ -46,7 +46,6 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests, MatrixItem.UserItem(permalinkData.userId, user?.displayName, user?.avatarUrl) } is PermalinkData.RoomLink -> { - // Exclude event link (used in reply event) if (permalinkData.eventId == null) { val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias) if (permalinkData.isRoomAlias) { @@ -55,6 +54,7 @@ class MxLinkTagHandler(private val glideRequests: GlideRequests, 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 } } From 0ccb975d43dd4bab43140d86d329f4385bc976e7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 11:04:06 +0100 Subject: [PATCH 55/61] Disable MatrixLinkify --- .../matrix/android/api/permalinks/MatrixLinkify.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt index fc02cf4a61..cd4ce1206e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt @@ -17,7 +17,6 @@ package im.vector.matrix.android.api.permalinks import android.text.Spannable -import im.vector.matrix.android.api.MatrixPatterns /** * MatrixLinkify take a piece of text and turns all of the @@ -30,7 +29,13 @@ object MatrixLinkify { * * @param spannable the text in which the matrix items has to be clickable. */ + @Suppress("UNUSED_PARAMETER") fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean { + /** + * 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 @@ -50,5 +55,7 @@ object MatrixLinkify { } } return hasMatch + */ + return false } } From 3cc65b1e71e7a84e569b6978395f5ad43a4cf444 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 11:05:54 +0100 Subject: [PATCH 56/61] ktlint --- ...ocompleteRoomController.kt => AutocompleteGroupController.kt} | 0 .../features/home/room/detail/composer/TextComposerViewModel.kt | 1 - 2 files changed, 1 deletion(-) rename vector/src/main/java/im/vector/riotx/features/autocomplete/group/{AutocompleteRoomController.kt => AutocompleteGroupController.kt} (100%) diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteRoomController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupController.kt similarity index 100% rename from vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteRoomController.kt rename to vector/src/main/java/im/vector/riotx/features/autocomplete/group/AutocompleteGroupController.kt diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt index 538d13bb4e..f7ec78c6c4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerViewModel.kt @@ -79,7 +79,6 @@ class TextComposerViewModel @AssistedInject constructor(@Assisted initialState: usersQueryObservable.accept(query) } - private fun handleQueryRooms(action: TextComposerAction.QueryRooms) { val query = Option.fromNullable(action.query) roomsQueryObservable.accept(query) From e13281dc9750c7c8f95c42578b52e2d436059df3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 20 Dec 2019 11:27:26 +0100 Subject: [PATCH 57/61] Update CHANGES and clean code --- CHANGES.md | 5 +++-- .../vector/riotx/features/home/room/list/RoomListFragment.kt | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 863b1ca455..10929998ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,13 +5,14 @@ Features ✨: - Improvements 🙌: - - + - The initial sync is now handled by a foreground service Other changes: - Bugfix 🐛: - - + - Fix avatar image disappearing (#777) + - Fix read marker banner when permalink Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 27fbc387b1..e272c1423f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -28,7 +28,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.mvrx.* -import com.google.android.material.snackbar.Snackbar import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary From 83126d5f5585cf1ea63ad7ce58cd25adf2ab2246 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 20 Dec 2019 11:54:59 +0100 Subject: [PATCH 58/61] Fix android tests not working --- .../SingleThreadCoroutineDispatcher.kt | 2 +- .../android/auth/AuthenticationServiceTest.kt | 60 ------------------- .../internal/crypto/CryptoStoreTest.kt | 16 ++++- .../session/room/timeline/ChunkEntityTest.kt | 8 ++- 4 files changed, 23 insertions(+), 63 deletions(-) delete mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticationServiceTest.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt index e63123f3b3..f2fbde3fe7 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/SingleThreadCoroutineDispatcher.kt @@ -19,4 +19,4 @@ package im.vector.matrix.android import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.Dispatchers.Main -internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, Main) +internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticationServiceTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticationServiceTest.kt deleted file mode 100644 index c3babd7e5a..0000000000 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/auth/AuthenticationServiceTest.kt +++ /dev/null @@ -1,60 +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.matrix.android.auth - -import androidx.test.annotation.UiThreadTest -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.rule.GrantPermissionRule -import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.OkReplayRuleChainNoActivity -import im.vector.matrix.android.api.auth.AuthenticationService -import okreplay.* -import org.junit.ClassRule -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -internal class AuthenticationServiceTest : InstrumentedTest { - - lateinit var authenticationService: AuthenticationService - lateinit var okReplayInterceptor: OkReplayInterceptor - - private val okReplayConfig = OkReplayConfig.Builder() - .tapeRoot(AndroidTapeRoot( - context(), javaClass)) - .defaultMode(TapeMode.READ_WRITE) // or TapeMode.READ_ONLY - .sslEnabled(true) - .interceptor(okReplayInterceptor) - .build() - - @get:Rule - val testRule = OkReplayRuleChainNoActivity(okReplayConfig).get() - - @Test - @UiThreadTest - @OkReplay(tape = "auth", mode = TapeMode.READ_WRITE) - fun auth() { - } - - companion object { - @ClassRule - @JvmField - val grantExternalStoragePermissionRule: GrantPermissionRule = - GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - } -} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt index 3fc3079cc7..bab0a366ce 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt @@ -16,20 +16,34 @@ package im.vector.matrix.android.internal.crypto +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import io.realm.Realm +import io.realm.RealmConfiguration import org.junit.Assert.* +import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.matrix.olm.OlmAccount import org.matrix.olm.OlmManager import org.matrix.olm.OlmSession private const val DUMMY_DEVICE_KEY = "DeviceKey" -class CryptoStoreTest { +@RunWith(AndroidJUnit4::class) +class CryptoStoreTest : InstrumentedTest { private val cryptoStoreHelper = CryptoStoreHelper() + @Before + fun setup() { + Realm.init(context()) + } + + @Test fun test_metadata_realm_ok() { val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore() diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt index abb990c979..9c81a8e18f 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt @@ -19,8 +19,10 @@ package im.vector.matrix.android.session.room.timeline import androidx.test.ext.junit.runners.AndroidJUnit4 import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.SessionRealmModule import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent @@ -43,7 +45,11 @@ internal class ChunkEntityTest : InstrumentedTest { @Before fun setup() { Realm.init(context()) - val testConfig = RealmConfiguration.Builder().inMemory().name("test-realm").build() + val testConfig = RealmConfiguration.Builder() + .inMemory() + .name("test-realm") + .modules(SessionRealmModule()) + .build() monarchy = Monarchy.Builder().setRealmConfiguration(testConfig).build() } From c54358831faece6549ae51b95d7dd48544e5d360 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 17:45:32 +0100 Subject: [PATCH 59/61] Group throwable extension together --- .../matrix/android/api/failure}/Extensions.kt | 16 +++++++++------- .../vector/matrix/android/api/failure/Failure.kt | 2 -- .../login/LoginGenericTextInputFormFragment.kt | 2 +- ...LoginResetPasswordMailConfirmationFragment.kt | 2 +- .../features/login/LoginWaitForEmailFragment.kt | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename {vector/src/main/java/im/vector/riotx/core/error => matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure}/Extensions.kt (60%) diff --git a/vector/src/main/java/im/vector/riotx/core/error/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt similarity index 60% rename from vector/src/main/java/im/vector/riotx/core/error/Extensions.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt index 614340bd3d..a3b5ce39eb 100644 --- a/vector/src/main/java/im/vector/riotx/core/error/Extensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Extensions.kt @@ -14,13 +14,15 @@ * limitations under the License. */ -package im.vector.riotx.core.error +package im.vector.matrix.android.api.failure -import im.vector.matrix.android.api.failure.Failure -import im.vector.matrix.android.api.failure.MatrixError import javax.net.ssl.HttpsURLConnection -fun Throwable.is401(): Boolean { - return (this is Failure.ServerError && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */ - && error.code == MatrixError.M_UNAUTHORIZED) -} +fun Throwable.is401() = + this is Failure.ServerError + && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */ + && error.code == MatrixError.M_UNAUTHORIZED + +fun Throwable.isTokenError() = + this is Failure.ServerError + && (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 611b2fb7d3..4d44e3346b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -44,5 +44,3 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { abstract class FeatureFailure : Failure() } - -fun Throwable.isTokenError() = this is Failure.ServerError && (this.error.code == MatrixError.M_UNKNOWN_TOKEN || this.error.code == MatrixError.M_MISSING_TOKEN) diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt index 64fb01fa5f..3ee1cd6d64 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginGenericTextInputFormFragment.kt @@ -30,8 +30,8 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil import com.jakewharton.rxbinding3.widget.textChanges import im.vector.matrix.android.api.auth.registration.RegisterThreePid import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.api.failure.is401 import im.vector.riotx.R -import im.vector.riotx.core.error.is401 import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.isEmail import im.vector.riotx.core.extensions.setTextOrHide diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt index e7ddc78853..cace48b7f2 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginResetPasswordMailConfirmationFragment.kt @@ -20,8 +20,8 @@ import androidx.appcompat.app.AlertDialog import butterknife.OnClick import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Success +import im.vector.matrix.android.api.failure.is401 import im.vector.riotx.R -import im.vector.riotx.core.error.is401 import kotlinx.android.synthetic.main.fragment_login_reset_password_mail_confirmation.* import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginWaitForEmailFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginWaitForEmailFragment.kt index 8a12c67106..c4507876d7 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginWaitForEmailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginWaitForEmailFragment.kt @@ -20,8 +20,8 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import com.airbnb.mvrx.args +import im.vector.matrix.android.api.failure.is401 import im.vector.riotx.R -import im.vector.riotx.core.error.is401 import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_login_wait_for_email.* import javax.inject.Inject From 4fe9c52737df92da4db13c79c2a8f45f7d37e4ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 20 Dec 2019 17:54:02 +0100 Subject: [PATCH 60/61] Move permission to the main AndroidManifest --- vector/src/fdroid/AndroidManifest.xml | 1 - vector/src/gplay/AndroidManifest.xml | 2 -- vector/src/main/AndroidManifest.xml | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 51234e0bba..32cdba9251 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -4,7 +4,6 @@ - diff --git a/vector/src/gplay/AndroidManifest.xml b/vector/src/gplay/AndroidManifest.xml index 85567df2c1..db30f21f7a 100755 --- a/vector/src/gplay/AndroidManifest.xml +++ b/vector/src/gplay/AndroidManifest.xml @@ -2,8 +2,6 @@ - - diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index e97e7d767d..124763916b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + Date: Fri, 20 Dec 2019 17:54:35 +0100 Subject: [PATCH 61/61] ktlint --- .../vector/matrix/android/internal/crypto/CryptoStoreTest.kt | 3 --- .../matrix/android/session/room/timeline/ChunkEntityTest.kt | 1 - 2 files changed, 4 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt index bab0a366ce..df503f2486 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/CryptoStoreTest.kt @@ -17,12 +17,10 @@ package im.vector.matrix.android.internal.crypto import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import io.realm.Realm -import io.realm.RealmConfiguration import org.junit.Assert.* import org.junit.Before import org.junit.Test @@ -43,7 +41,6 @@ class CryptoStoreTest : InstrumentedTest { Realm.init(context()) } - @Test fun test_metadata_realm_ok() { val cryptoStore: IMXCryptoStore = cryptoStoreHelper.createStore() diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt index 9c81a8e18f..592086b0ec 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/session/room/timeline/ChunkEntityTest.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.session.room.timeline import androidx.test.ext.junit.runners.AndroidJUnit4 import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.SessionRealmModule