Merge tag 'v1.0.8' into fix_2100_2

This commit is contained in:
Markus 2020-10-11 09:15:57 +02:00
commit e6a8fd5b73
93 changed files with 1438 additions and 953 deletions

View File

@ -1,29 +1,28 @@
Changes in Element 1.0.8 (2020-XX-XX)
Changes in Element 1.0.8 (2020-09-25)
===================================================
Features ✨:
-
Improvements 🙌:
- Add "show password" in import Megolm keys dialog
- Visually disable call buttons in menu and prohibit calling when permissions are insufficient (#2112)
- Better management of requested permissions (#2048)
- Add a setting to show timestamp for all messages (#2123)
- Use cache for user color
- Allow using an outdated homeserver, at user's risk (#1972)
- Restore small logo on login screens and fix scrolling issue on those screens
- PIN Code Improvements: Add more settings: biometrics, grace period, notification content (#1985)
Bugfix 🐛:
- Long message cannot be sent/takes infinite time & blocks other messages #1397
- Long message cannot be sent/takes infinite time & blocks other messages (#1397)
- Fix crash when wellknown are malformed, or redirect to some HTML content (reported by rageshakes)
- User Verification in DM not working
- Manual import of Megolm keys does back up the imported keys
- Auto scrolling to the latest message when sending (#2094)
- Fix incorrect permission check when creating widgets (#2137)
Translations 🗣:
-
- Pin code: user has to enter pin code twice (#2005)
SDK API changes ⚠️:
- Rename `tryThis` to `tryOrNull`
Build 🧱:
-
Other changes:
- Add an advanced action to reset an account data entry

View File

@ -17,13 +17,11 @@
package org.matrix.android.sdk.api.auth.data
// Either a list of supported login types, or an error if the homeserver is outdated
sealed class LoginFlowResult {
data class Success(
val supportedLoginTypes: List<String>,
val isLoginAndRegistrationSupported: Boolean,
val homeServerUrl: String
val homeServerUrl: String,
val isOutdatedHomeserver: Boolean
) : LoginFlowResult()
object OutdatedHomeserver : LoginFlowResult()
}

View File

@ -273,16 +273,16 @@ internal class DefaultAuthenticationService @Inject constructor(
}
private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult {
return if (versions.isSupportedBySdk()) {
// Get the login flow
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
apiCall = authAPI.getLoginFlows()
}
LoginFlowResult.Success(loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl)
} else {
// Not supported
LoginFlowResult.OutdatedHomeserver
// Get the login flow
val loginFlowResponse = executeRequest<LoginFlowResponse>(null) {
apiCall = authAPI.getLoginFlows()
}
return LoginFlowResult.Success(
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
versions.isLoginAndRegistrationSupportedBySdk(),
homeServerUrl,
!versions.isSupportedBySdk()
)
}
override fun getRegistrationWizard(): RegistrationWizard {

View File

@ -18,10 +18,9 @@
package org.matrix.android.sdk.internal.crypto
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event
@ -32,28 +31,29 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import javax.inject.Inject
internal class CancelGossipRequestWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
val sessionId: String,
override val sessionId: String,
val requestId: String,
val recipients: Map<String, List<String>>
) {
val recipients: Map<String, List<String>>,
override val lastFailureMessage: String? = null
) : SessionWorkerParams {
companion object {
fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params {
return Params(
sessionId = sessionId,
requestId = request.requestId,
recipients = request.recipients
recipients = request.recipients,
lastFailureMessage = null
)
}
}
@ -64,18 +64,11 @@ internal class CancelGossipRequestWorker(context: Context,
@Inject lateinit var eventBus: EventBus
@Inject lateinit var credentials: Credentials
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val localId = LocalEcho.createLocalEchoId()
val contentMap = MXUsersDevicesMap<Any>()
val toDeviceContent = ShareRequestCancellation(
@ -107,13 +100,17 @@ internal class CancelGossipRequestWorker(context: Context,
)
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED)
return Result.success()
} catch (exception: Throwable) {
return if (exception.shouldBeRetried()) {
} catch (throwable: Throwable) {
return if (throwable.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL)
Result.success(errorOutputData)
buildErrorResult(params, throwable.localizedMessage ?: "error")
}
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -18,10 +18,9 @@
package org.matrix.android.sdk.internal.crypto
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event
@ -34,40 +33,34 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest
import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject
internal class SendGossipRequestWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
val sessionId: String,
override val sessionId: String,
val keyShareRequest: OutgoingRoomKeyRequest? = null,
val secretShareRequest: OutgoingSecretRequest? = null
)
val secretShareRequest: OutgoingSecretRequest? = null,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
@Inject lateinit var cryptoStore: IMXCryptoStore
@Inject lateinit var eventBus: EventBus
@Inject lateinit var credentials: Credentials
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val localId = LocalEcho.createLocalEchoId()
val contentMap = MXUsersDevicesMap<Any>()
val eventType: String
@ -121,7 +114,7 @@ internal class SendGossipRequestWorker(context: Context,
}
}
else -> {
return Result.success(errorOutputData).also {
return buildErrorResult(params, "Unknown empty gossiping request").also {
Timber.e("Unknown empty gossiping request: $params")
}
}
@ -137,13 +130,17 @@ internal class SendGossipRequestWorker(context: Context,
)
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT)
return Result.success()
} catch (exception: Throwable) {
return if (exception.shouldBeRetried()) {
} catch (throwable: Throwable) {
return if (throwable.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND)
Result.success(errorOutputData)
buildErrorResult(params, throwable.localizedMessage ?: "error")
}
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -18,10 +18,9 @@
package org.matrix.android.sdk.internal.crypto
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.events.model.Event
@ -34,22 +33,23 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject
internal class SendGossipWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<SendGossipWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
val sessionId: String,
override val sessionId: String,
val secretValue: String,
val request: IncomingSecretShareRequest
)
val request: IncomingSecretShareRequest,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
@Inject lateinit var cryptoStore: IMXCryptoStore
@ -58,18 +58,11 @@ internal class SendGossipWorker(context: Context,
@Inject lateinit var messageEncrypter: MessageEncrypter
@Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean("failed", true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val localId = LocalEcho.createLocalEchoId()
val eventType: String = EventType.SEND_SECRET
@ -81,7 +74,7 @@ internal class SendGossipWorker(context: Context,
val requestingUserId = params.request.userId ?: ""
val requestingDeviceId = params.request.deviceId ?: ""
val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId)
?: return Result.success(errorOutputData).also {
?: return buildErrorResult(params, "Unknown deviceInfo, cannot send message").also {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}")
}
@ -94,7 +87,7 @@ internal class SendGossipWorker(context: Context,
if (olmSessionResult?.sessionId == null) {
// no session with this device, probably because there
// were no one-time keys.
return Result.success(errorOutputData).also {
return buildErrorResult(params, "no session with this device").also {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Timber.e("no session with this device, probably because there were no one-time keys.")
}
@ -130,13 +123,17 @@ internal class SendGossipWorker(context: Context,
)
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED)
return Result.success()
} catch (exception: Throwable) {
return if (exception.shouldBeRetried()) {
} catch (throwable: Throwable) {
return if (throwable.shouldBeRetried()) {
Result.retry()
} else {
cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED)
Result.success(errorOutputData)
buildErrorResult(params, throwable.localizedMessage ?: "error")
}
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -17,18 +17,17 @@
package org.matrix.android.sdk.internal.crypto.verification
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.shouldBeRetried
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber
import javax.inject.Inject
@ -38,7 +37,7 @@ import javax.inject.Inject
*/
internal class SendVerificationMessageWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -47,30 +46,17 @@ internal class SendVerificationMessageWorker(context: Context,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
@Inject
lateinit var sendVerificationMessageTask: SendVerificationMessageTask
@Inject
lateinit var localEchoRepository: LocalEchoRepository
@Inject
lateinit var cryptoService: CryptoService
@Inject lateinit var sendVerificationMessageTask: SendVerificationMessageTask
@Inject lateinit var localEchoRepository: LocalEchoRepository
@Inject lateinit var cryptoService: CryptoService
@Inject lateinit var cancelSendTracker: CancelSendTracker
override suspend fun doWork(): Result {
val errorOutputData = Data.Builder().putBoolean(OUTPUT_KEY_FAILED, true).build()
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success(errorOutputData)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
val sessionComponent = getSessionComponent(params.sessionId)
?: return Result.success(errorOutputData).also {
// TODO, can this happen? should I update local echo?
Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}")
}
sessionComponent.inject(this)
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId) ?: return Result.success(errorOutputData)
override suspend fun doSafeWork(params: Params): Result {
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId) ?: return buildErrorResult(params, "Event not found")
val localEventId = localEvent.eventId ?: ""
val roomId = localEvent.roomId ?: ""
@ -91,20 +77,16 @@ internal class SendVerificationMessageWorker(context: Context,
)
Result.success(Data.Builder().putString(localEventId, resultEventId).build())
} catch (exception: Throwable) {
if (exception.shouldBeRetried()) {
} catch (throwable: Throwable) {
if (throwable.shouldBeRetried()) {
Result.retry()
} else {
Result.success(errorOutputData)
buildErrorResult(params, throwable.localizedMessage ?: "error")
}
}
}
companion object {
private const val OUTPUT_KEY_FAILED = "failed"
fun hasFailed(outputData: Data): Boolean {
return outputData.getBoolean(OUTPUT_KEY_FAILED, false)
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -54,6 +54,7 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.StringProvider
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
import java.util.UUID
@ -117,14 +118,13 @@ internal class VerificationTransportRoomMessage(
workInfoList
?.firstOrNull { it.id == enqueueInfo.second }
?.let { wInfo ->
when (wInfo.state) {
WorkInfo.State.FAILED -> {
tx?.cancel(onErrorReason)
workLiveData.removeObserver(this)
}
WorkInfo.State.SUCCEEDED -> {
if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
Timber.e("## SAS verification [${tx?.transactionId}] failed to send verification message in state : ${tx?.state}")
tx?.cancel(onErrorReason)
} else {
@ -210,7 +210,7 @@ internal class VerificationTransportRoomMessage(
?.filter { it.state == WorkInfo.State.SUCCEEDED }
?.firstOrNull { it.id == workRequest.id }
?.let { wInfo ->
if (SendVerificationMessageWorker.hasFailed(wInfo.outputData)) {
if (SessionSafeCoroutineWorker.hasFailed(wInfo.outputData)) {
callback(null, null)
} else {
val eventId = wInfo.outputData.getString(localId)

View File

@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.content
import android.content.Context
import android.graphics.BitmapFactory
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.tryOrNull
@ -37,13 +36,14 @@ import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.network.ProgressRequestBody
import org.matrix.android.sdk.internal.session.DefaultFileService
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber
import java.io.File
import java.util.UUID
@ -59,7 +59,8 @@ private data class NewImageAttributes(
* Possible previous worker: None
* Possible next worker : Always [MultipleEventSendingDispatcherWorker]
*/
internal class UploadContentWorker(val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
internal class UploadContentWorker(val context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -78,19 +79,12 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
@Inject lateinit var imageCompressor: ImageCompressor
@Inject lateinit var localEchoRepository: LocalEchoRepository
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
.also { Timber.e("Unable to parse work parameters") }
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
Timber.v("Starting upload media work with params $params")
if (params.lastFailureMessage != null) {
// Transmit the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
}
// Just defensive code to ensure that we never have an uncaught exception that could break the queue
return try {
internalDoWork(params)
@ -100,10 +94,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
}
}
private suspend fun internalDoWork(params: Params): Result {
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
private suspend fun internalDoWork(params: Params): Result {
val allCancelled = params.localEchoIds.all { cancelSendTracker.isCancelRequestedFor(it.eventId, it.roomId) }
if (allCancelled) {
// there is no point in uploading the image!
@ -218,14 +213,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
}
} catch (e: Exception) {
Timber.e(e, "## FileService: ERROR")
notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) }
return Result.success(
WorkerParamsFactory.toData(
params.copy(
lastFailureMessage = e.localizedMessage
)
)
)
return handleFailure(params, e)
} finally {
// Delete all temporary files
filesToDelete.forEach {

View File

@ -18,19 +18,19 @@
package org.matrix.android.sdk.internal.session.group
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import javax.inject.Inject
/**
* Possible previous worker: None
* Possible next worker : None
*/
internal class GetGroupDataWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
internal class GetGroupDataWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<GetGroupDataWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -40,12 +40,11 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
@Inject lateinit var getGroupDataTask: GetGroupDataTask
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
override suspend fun doSafeWork(params: Params): Result {
return runCatching {
getGroupDataTask.execute(GetGroupDataTask.Params.FetchAllActive)
}.fold(
@ -53,4 +52,8 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
{ Result.retry() }
)
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -17,10 +17,10 @@
package org.matrix.android.sdk.internal.session.pushers
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import com.zhuinden.monarchy.Monarchy
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.pushers.PusherState
import org.matrix.android.sdk.internal.database.mapper.toEntity
@ -28,16 +28,14 @@ import org.matrix.android.sdk.internal.database.model.PusherEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.util.awaitTransaction
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -50,14 +48,11 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
@Inject @SessionDatabase lateinit var monarchy: Monarchy
@Inject lateinit var eventBus: EventBus
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()
.also { Timber.e("Unable to parse work parameters") }
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val pusher = params.pusher
if (pusher.pushKey.isBlank()) {
@ -82,6 +77,10 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
private suspend fun setPusher(pusher: JsonPusher) {
executeRequest<Unit>(eventBus) {
apiCall = pushersAPI.setPusher(pusher)

View File

@ -17,7 +17,6 @@
package org.matrix.android.sdk.internal.session.room.relation
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.greenrobot.eventbus.EventBus
@ -27,17 +26,17 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
import org.matrix.android.sdk.internal.session.room.send.SendResponse
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber
import javax.inject.Inject
// TODO This is not used. Delete?
internal class SendRelationWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
internal class SendRelationWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<SendRelationWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -52,19 +51,11 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
@Inject lateinit var eventBus: EventBus
@Inject lateinit var localEchoRepository: LocalEchoRepository
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()
if (params.lastFailureMessage != null) {
// Transmit the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
}
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
if (localEvent?.eventId == null) {
return Result.failure()
@ -89,6 +80,10 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
executeRequest<SendResponse>(eventBus) {
apiCall = roomAPI.sendRelation(

View File

@ -18,7 +18,6 @@
package org.matrix.android.sdk.internal.session.room.send
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.Failure
@ -31,10 +30,11 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber
import javax.inject.Inject
@ -43,7 +43,7 @@ import javax.inject.Inject
* Possible next worker : Always [SendEventWorker]
*/
internal class EncryptEventWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<EncryptEventWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -58,20 +58,12 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
@Inject lateinit var localEchoRepository: LocalEchoRepository
@Inject lateinit var cancelSendTracker: CancelSendTracker
override suspend fun doWork(): Result {
Timber.v("Start Encrypt work")
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
if (params.lastFailureMessage != null) {
// Transmit the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
}
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
Timber.v("## SendEvent: Start Encrypt work for event ${params.eventId}")
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
if (localEvent?.eventId == null) {
@ -148,4 +140,8 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -19,17 +19,17 @@ package org.matrix.android.sdk.internal.session.room.send
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.CoroutineWorker
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.matrix.android.sdk.internal.worker.startChain
import timber.log.Timber
import java.util.concurrent.TimeUnit
@ -42,7 +42,7 @@ import javax.inject.Inject
* Possible next worker : None, but it will post new work to send events, encrypted or not
*/
internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -56,22 +56,20 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
@Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon
@Inject lateinit var localEchoRepository: LocalEchoRepository
override suspend fun doWork(): Result {
Timber.v("## SendEvent: Start dispatch sending multiple event work")
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
if (params.lastFailureMessage != null) {
params.localEchoIds.forEach { localEchoIds ->
localEchoRepository.updateSendState(localEchoIds.eventId, SendState.UNDELIVERED)
}
// Transmit the error if needed?
return Result.success(inputData)
.also { Timber.e("## SendEvent: Work cancelled due to input error from parent ${params.lastFailureMessage}") }
override fun doOnError(params: Params): Result {
params.localEchoIds.forEach { localEchoIds ->
localEchoRepository.updateSendState(localEchoIds.eventId, SendState.UNDELIVERED)
}
return super.doOnError(params)
}
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
Timber.v("## SendEvent: Start dispatch sending multiple event work")
// Create a work for every event
params.localEchoIds.forEach { localEchoIds ->
val roomId = localEchoIds.roomId
@ -94,6 +92,10 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
return Result.success()
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
private fun createEncryptEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
val params = EncryptEventWorker.Params(sessionId, eventId)
val sendWorkData = WorkerParamsFactory.toData(params)

View File

@ -17,24 +17,24 @@
package org.matrix.android.sdk.internal.session.room.send
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import javax.inject.Inject
/**
* Possible previous worker: None
* Possible next worker : None
*/
internal class RedactEventWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
internal class RedactEventWorker(context: Context, params: WorkerParameters)
: SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -49,19 +49,11 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
@Inject lateinit var roomAPI: RoomAPI
@Inject lateinit var eventBus: EventBus
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()
if (params.lastFailureMessage != null) {
// Transmit the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
}
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val eventId = params.eventId
return runCatching {
executeRequest<SendResponse>(eventBus) {
@ -90,4 +82,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
}
)
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
}

View File

@ -18,7 +18,6 @@
package org.matrix.android.sdk.internal.session.room.send
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import io.realm.RealmConfiguration
@ -28,10 +27,10 @@ import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber
import javax.inject.Inject
@ -43,7 +42,7 @@ import javax.inject.Inject
*/
internal class SendEventWorker(context: Context,
params: WorkerParameters)
: CoroutineWorker(context, params) {
: SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -58,13 +57,11 @@ internal class SendEventWorker(context: Context,
@Inject lateinit var cancelSendTracker: CancelSendTracker
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
val event = localEchoRepository.getUpToDateEcho(params.eventId)
if (event?.eventId == null || event.roomId == null) {
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
@ -103,6 +100,10 @@ internal class SendEventWorker(context: Context,
}
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
localEchoRepository.updateSendState(eventId, SendState.SENDING)
executeRequest<SendResponse>(eventBus) {

View File

@ -131,7 +131,7 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private
/**
* At the moment we don't get any group data through the sync, so we poll where every hour.
You can also force to refetch group data using [Group] API.
* You can also force to refetch group data using [Group] API.
*/
private fun scheduleGroupDataFetchingIfNeeded(groupsSyncResponse: GroupsSyncResponse) {
val groupIds = ArrayList<String>()

View File

@ -18,18 +18,18 @@ package org.matrix.android.sdk.internal.session.sync.job
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.failure.isTokenError
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.sync.SyncTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import org.matrix.android.sdk.internal.worker.getSessionComponent
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -43,7 +43,7 @@ private const val DEFAULT_DELAY_TIMEOUT = 30_000L
*/
internal class SyncWorker(context: Context,
workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
) : SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, Params::class.java) {
@JsonClass(generateAdapter = true)
internal data class Params(
@ -59,14 +59,13 @@ internal class SyncWorker(context: Context,
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
@Inject lateinit var workManagerProvider: WorkManagerProvider
override suspend fun doWork(): Result {
Timber.i("Sync work starting")
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success()
.also { Timber.e("Unable to parse work parameters") }
override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}
override suspend fun doSafeWork(params: Params): Result {
Timber.i("Sync work starting")
val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success()
sessionComponent.inject(this)
return runCatching {
doSync(params.timeout)
}.fold(
@ -91,6 +90,10 @@ internal class SyncWorker(context: Context,
)
}
override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}
private suspend fun doSync(timeout: Long) {
val taskParams = SyncTask.Params(timeout * 1000)
syncTask.execute(taskParams)

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.worker
import android.content.Context
import androidx.annotation.CallSuper
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.session.SessionComponent
import timber.log.Timber
/**
* This worker should only sends Result.Success when added to a unique queue to avoid breaking the unique queue.
* This abstract class handle the cases of problem when parsing parameter, and forward the error if any to
* the next workers.
*/
internal abstract class SessionSafeCoroutineWorker<PARAM : SessionWorkerParams>(
context: Context,
workerParameters: WorkerParameters,
private val paramClass: Class<PARAM>
) : CoroutineWorker(context, workerParameters) {
@JsonClass(generateAdapter = true)
internal data class ErrorData(
override val sessionId: String,
override val lastFailureMessage: String? = null
) : SessionWorkerParams
final override suspend fun doWork(): Result {
val params = WorkerParamsFactory.fromData(paramClass, inputData)
?: return buildErrorResult(null, "Unable to parse work parameters")
.also { Timber.e("Unable to parse work parameters") }
return try {
val sessionComponent = getSessionComponent(params.sessionId)
?: return buildErrorResult(params, "No session")
// Make sure to inject before handling error as you may need some dependencies to process them.
injectWith(sessionComponent)
if (params.lastFailureMessage != null) {
// Forward error to the next workers
doOnError(params)
} else {
doSafeWork(params)
}
} catch (throwable: Throwable) {
buildErrorResult(params, throwable.localizedMessage ?: "error")
}
}
abstract fun injectWith(injector: SessionComponent)
/**
* Should only return Result.Success for workers added to a unique queue
*/
abstract suspend fun doSafeWork(params: PARAM): Result
protected fun buildErrorResult(params: PARAM?, message: String): Result {
return Result.success(
if (params != null) {
WorkerParamsFactory.toData(paramClass, buildErrorParams(params, message))
} else {
WorkerParamsFactory.toData(ErrorData::class.java, ErrorData(sessionId = "", lastFailureMessage = message))
}
)
}
abstract fun buildErrorParams(params: PARAM, message: String): PARAM
/**
* This is called when the input parameters are correct, but contain an error from the previous worker.
*/
@CallSuper
open fun doOnError(params: PARAM): Result {
// Forward the error
return Result.success(inputData)
.also { Timber.e("Work cancelled due to input error from parent") }
}
companion object {
fun hasFailed(outputData: Data): Boolean {
return WorkerParamsFactory.fromData(ErrorData::class.java, outputData)
.let { it?.lastFailureMessage != null }
}
}
}

View File

@ -18,13 +18,14 @@
package org.matrix.android.sdk.internal.worker
import androidx.work.Data
import com.squareup.moshi.Moshi
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.network.parsing.CheckNumberType
internal object WorkerParamsFactory {
val moshi by lazy {
private val moshi: Moshi by lazy {
// We are adding the CheckNumberType as we are serializing/deserializing multiple time in a row
// and we lost typing information doing so.
// We don't want this check to be done on all adapters, so we just add it here.
@ -34,20 +35,24 @@ internal object WorkerParamsFactory {
.build()
}
const val KEY = "WORKER_PARAMS_JSON"
private const val KEY = "WORKER_PARAMS_JSON"
inline fun <reified T> toData(params: T): Data {
val adapter = moshi.adapter(T::class.java)
inline fun <reified T> toData(params: T) = toData(T::class.java, params)
fun <T> toData(clazz: Class<T>, params: T): Data {
val adapter = moshi.adapter(clazz)
val json = adapter.toJson(params)
return Data.Builder().putString(KEY, json).build()
}
inline fun <reified T> fromData(data: Data): T? = tryOrNull("Unable to parse work parameters") {
inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data)
fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull("Unable to parse work parameters") {
val json = data.getString(KEY)
return if (json == null) {
null
} else {
val adapter = moshi.adapter(T::class.java)
val adapter = moshi.adapter(clazz)
adapter.fromJson(json)
}
}

View File

@ -151,21 +151,21 @@
<string name="notice_power_level_changed">%1$s alterou o nível de permissão de %2$s.</string>
<string name="notice_power_level_diff">%1$s de %2$s para %3$s</string>
<string name="initial_sync_start_importing_account">Primeira sincronização:
<string name="initial_sync_start_importing_account">Primeira sincronização:
\nImportando a conta…</string>
<string name="initial_sync_start_importing_account_crypto">Primeira sincronização:
<string name="initial_sync_start_importing_account_crypto">Primeira sincronização:
\nImportando as chaves de criptografia</string>
<string name="initial_sync_start_importing_account_rooms">Primeira sincronização:
<string name="initial_sync_start_importing_account_rooms">Primeira sincronização:
\nImportando as salas</string>
<string name="initial_sync_start_importing_account_joined_rooms">Primeira sincronização:
<string name="initial_sync_start_importing_account_joined_rooms">Primeira sincronização:
\nImportando as salas em que você entrou</string>
<string name="initial_sync_start_importing_account_invited_rooms">Primeira sincronização:
<string name="initial_sync_start_importing_account_invited_rooms">Primeira sincronização:
\nImportando as salas em que você foi convidado</string>
<string name="initial_sync_start_importing_account_left_rooms">Primeira sincronização:
<string name="initial_sync_start_importing_account_left_rooms">Primeira sincronização:
\nImportando as salas em que você saiu</string>
<string name="initial_sync_start_importing_account_groups">Primeira sincronização:
<string name="initial_sync_start_importing_account_groups">Primeira sincronização:
\nImportando as comunidades</string>
<string name="initial_sync_start_importing_account_data">Primeira sincronização:
<string name="initial_sync_start_importing_account_data">Primeira sincronização:
\nImportando os dados da conta</string>
<string name="event_status_sending_message">Enviando mensagem…</string>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Generated file, do not edit -->
<string name="verification_emoji_dog">Cachorro</string>
<string name="verification_emoji_cat">Gato</string>
<string name="verification_emoji_lion">Leão</string>
<string name="verification_emoji_horse">Cavalo</string>
<string name="verification_emoji_unicorn">Unicórnio</string>
<string name="verification_emoji_pig">Porco</string>
<string name="verification_emoji_elephant">Elefante</string>
<string name="verification_emoji_rabbit">Coelho</string>
<string name="verification_emoji_panda">Panda</string>
<string name="verification_emoji_rooster">Galo</string>
<string name="verification_emoji_penguin">Pinguim</string>
<string name="verification_emoji_turtle">Tartaruga</string>
<string name="verification_emoji_fish">Peixe</string>
<string name="verification_emoji_octopus">Polvo</string>
<string name="verification_emoji_butterfly">Borboleta</string>
<string name="verification_emoji_flower">Flor</string>
<string name="verification_emoji_tree">Árvore</string>
<string name="verification_emoji_cactus">Cacto</string>
<string name="verification_emoji_mushroom">Cogumelo</string>
<string name="verification_emoji_globe">Globo</string>
<string name="verification_emoji_moon">Lua</string>
<string name="verification_emoji_cloud">Nuvem</string>
<string name="verification_emoji_fire">Fogo</string>
<string name="verification_emoji_banana">Banana</string>
<string name="verification_emoji_apple">Maçã</string>
<string name="verification_emoji_strawberry">Morango</string>
<string name="verification_emoji_corn">Milho</string>
<string name="verification_emoji_pizza">Pizza</string>
<string name="verification_emoji_cake">Bolo</string>
<string name="verification_emoji_heart">Coração</string>
<string name="verification_emoji_smiley">Sorriso</string>
<string name="verification_emoji_robot">Robô</string>
<string name="verification_emoji_hat">Chapéu</string>
<string name="verification_emoji_glasses">Óculos</string>
<string name="verification_emoji_spanner">Chave inglesa</string>
<string name="verification_emoji_santa">Papai-noel</string>
<string name="verification_emoji_thumbs_up">Joinha</string>
<string name="verification_emoji_umbrella">Guarda-chuva</string>
<string name="verification_emoji_hourglass">Ampulheta</string>
<string name="verification_emoji_clock">Relógio</string>
<string name="verification_emoji_gift">Presente</string>
<string name="verification_emoji_light_bulb">Lâmpada</string>
<string name="verification_emoji_book">Livro</string>
<string name="verification_emoji_pencil">Lápis</string>
<string name="verification_emoji_paperclip">Clipe de papel</string>
<string name="verification_emoji_scissors">Tesoura</string>
<string name="verification_emoji_lock">Cadeado</string>
<string name="verification_emoji_key">Chave</string>
<string name="verification_emoji_hammer">Martelo</string>
<string name="verification_emoji_telephone">Telefone</string>
<string name="verification_emoji_flag">Bandeira</string>
<string name="verification_emoji_train">Trem</string>
<string name="verification_emoji_bicycle">Bicicleta</string>
<string name="verification_emoji_aeroplane">Avião</string>
<string name="verification_emoji_rocket">Foguete</string>
<string name="verification_emoji_trophy">Troféu</string>
<string name="verification_emoji_ball">Bola</string>
<string name="verification_emoji_guitar">Guitarra</string>
<string name="verification_emoji_trumpet">Trombeta</string>
<string name="verification_emoji_bell">Sino</string>
<string name="verification_emoji_anchor">Âncora</string>
<string name="verification_emoji_headphones">Fones de ouvido</string>
<string name="verification_emoji_folder">Pasta</string>
<string name="verification_emoji_pin">Alfinete</string>
</resources>

View File

@ -27,8 +27,8 @@
<string name="notice_room_ban_by_you">Du bannade %1$s</string>
<string name="notice_room_withdraw">%1$s drog tillbaka inbjudan för %2$s</string>
<string name="notice_room_withdraw_by_you">Du drog tillbaka inbjudan för %1$s</string>
<string name="notice_avatar_url_changed">%1$s ändrade sin avatar</string>
<string name="notice_avatar_url_changed_by_you">Du ändrade din avatar</string>
<string name="notice_avatar_url_changed">%1$s bytte sin avatar</string>
<string name="notice_avatar_url_changed_by_you">Du bytte din avatar</string>
<string name="notice_display_name_set">%1$s satte sitt visningsnamn till %2$s</string>
<string name="notice_display_name_set_by_you">Du satte ditt visningsnamn till %1$s</string>
<string name="notice_display_name_changed_from">%1$s bytte sitt visningsnamn från %2$s till %3$s</string>

View File

@ -27,7 +27,7 @@
<string name="verification_emoji_banana">Banan</string>
<string name="verification_emoji_apple">Äpple</string>
<string name="verification_emoji_strawberry">Jordgubbe</string>
<string name="verification_emoji_corn">Majskolv</string>
<string name="verification_emoji_corn">Majs</string>
<string name="verification_emoji_pizza">Pizza</string>
<string name="verification_emoji_cake">Tårta</string>
<string name="verification_emoji_heart">Hjärta</string>
@ -41,7 +41,7 @@
<string name="verification_emoji_umbrella">Paraply</string>
<string name="verification_emoji_hourglass">Timglas</string>
<string name="verification_emoji_clock">Klocka</string>
<string name="verification_emoji_gift">Paket</string>
<string name="verification_emoji_gift">Present</string>
<string name="verification_emoji_light_bulb">Lampa</string>
<string name="verification_emoji_book">Bok</string>
<string name="verification_emoji_pencil">Penna</string>
@ -52,7 +52,7 @@
<string name="verification_emoji_hammer">Hammare</string>
<string name="verification_emoji_telephone">Telefon</string>
<string name="verification_emoji_flag">Flagga</string>
<string name="verification_emoji_train">Ånglok</string>
<string name="verification_emoji_train">Tåg</string>
<string name="verification_emoji_bicycle">Cykel</string>
<string name="verification_emoji_aeroplane">Flygplan</string>
<string name="verification_emoji_rocket">Raket</string>

View File

@ -7,4 +7,62 @@
<string name="verification_emoji_horse"></string>
<string name="verification_emoji_unicorn">独角兽</string>
<string name="verification_emoji_pig"></string>
<string name="verification_emoji_elephant">大象</string>
<string name="verification_emoji_rabbit">兔子</string>
<string name="verification_emoji_panda">熊猫</string>
<string name="verification_emoji_rooster">公鸡</string>
<string name="verification_emoji_penguin">企鹅</string>
<string name="verification_emoji_turtle">乌龟</string>
<string name="verification_emoji_fish"></string>
<string name="verification_emoji_octopus">章鱼</string>
<string name="verification_emoji_butterfly">蝴蝶</string>
<string name="verification_emoji_flower"></string>
<string name="verification_emoji_tree"></string>
<string name="verification_emoji_cactus">仙人掌</string>
<string name="verification_emoji_mushroom">蘑菇</string>
<string name="verification_emoji_globe">地球</string>
<string name="verification_emoji_moon">月亮</string>
<string name="verification_emoji_cloud"></string>
<string name="verification_emoji_fire"></string>
<string name="verification_emoji_banana">香蕉</string>
<string name="verification_emoji_apple">苹果</string>
<string name="verification_emoji_strawberry">草莓</string>
<string name="verification_emoji_corn">玉米</string>
<string name="verification_emoji_pizza">披萨</string>
<string name="verification_emoji_cake">蛋糕</string>
<string name="verification_emoji_heart"></string>
<string name="verification_emoji_smiley">笑脸</string>
<string name="verification_emoji_robot">机器人</string>
<string name="verification_emoji_hat">帽子</string>
<string name="verification_emoji_glasses">眼镜</string>
<string name="verification_emoji_spanner">扳手</string>
<string name="verification_emoji_santa">圣诞老人</string>
<string name="verification_emoji_thumbs_up"></string>
<string name="verification_emoji_umbrella"></string>
<string name="verification_emoji_hourglass">沙漏</string>
<string name="verification_emoji_clock">时钟</string>
<string name="verification_emoji_gift">礼物</string>
<string name="verification_emoji_light_bulb">灯泡</string>
<string name="verification_emoji_book"></string>
<string name="verification_emoji_pencil">铅笔</string>
<string name="verification_emoji_paperclip">回形针</string>
<string name="verification_emoji_scissors">剪刀</string>
<string name="verification_emoji_lock"></string>
<string name="verification_emoji_key">钥匙</string>
<string name="verification_emoji_hammer">锤子</string>
<string name="verification_emoji_telephone">电话</string>
<string name="verification_emoji_flag">旗帜</string>
<string name="verification_emoji_train">火车</string>
<string name="verification_emoji_bicycle">自行车</string>
<string name="verification_emoji_aeroplane">飞机</string>
<string name="verification_emoji_rocket">火箭</string>
<string name="verification_emoji_trophy">奖杯</string>
<string name="verification_emoji_ball"></string>
<string name="verification_emoji_guitar">吉他</string>
<string name="verification_emoji_trumpet">喇叭</string>
<string name="verification_emoji_bell">铃铛</string>
<string name="verification_emoji_anchor"></string>
<string name="verification_emoji_headphones">耳机</string>
<string name="verification_emoji_folder">文件夹</string>
<string name="verification_emoji_pin">图钉</string>
</resources>

View File

@ -352,7 +352,7 @@ dependencies {
implementation 'me.saket:better-link-movement-method:2.2.0'
implementation 'com.google.android:flexbox:1.1.1'
implementation "androidx.autofill:autofill:$autofill_version"
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta9'
implementation 'com.github.vector-im:PFLockScreen-Android:1.0.0-beta10'
// Custom Tab
implementation 'androidx.browser:browser:1.2.0'

View File

@ -89,6 +89,7 @@ import im.vector.app.features.settings.VectorSettingsHelpAboutFragment
import im.vector.app.features.settings.VectorSettingsLabsFragment
import im.vector.app.features.settings.VectorSettingsNotificationPreferenceFragment
import im.vector.app.features.settings.VectorSettingsNotificationsTroubleshootFragment
import im.vector.app.features.settings.VectorSettingsPinFragment
import im.vector.app.features.settings.VectorSettingsPreferencesFragment
import im.vector.app.features.settings.VectorSettingsSecurityPrivacyFragment
import im.vector.app.features.settings.account.deactivation.DeactivateAccountFragment
@ -284,6 +285,11 @@ interface FragmentModule {
@FragmentKey(VectorSettingsLabsFragment::class)
fun bindVectorSettingsLabsFragment(fragment: VectorSettingsLabsFragment): Fragment
@Binds
@IntoMap
@FragmentKey(VectorSettingsPinFragment::class)
fun bindVectorSettingsPinFragment(fragment: VectorSettingsPinFragment): Fragment
@Binds
@IntoMap
@FragmentKey(PushRulesFragment::class)

View File

@ -36,6 +36,7 @@ import im.vector.app.features.crypto.verification.IncomingVerificationRequestHan
import im.vector.app.features.grouplist.SelectedGroupDataSource
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.HomeRoomListDataSource
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.VectorHtmlCompressor
import im.vector.app.features.login.ReAuthHelper
@ -71,6 +72,8 @@ interface VectorComponent {
fun matrix(): Matrix
fun matrixItemColorProvider(): MatrixItemColorProvider
fun sessionListener(): SessionListener
fun currentSession(): Session

View File

@ -318,11 +318,17 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
when (resultCode) {
Activity.RESULT_OK -> {
Timber.v("Pin ok, unlock app")
pinLocker.unlock()
// Cancel any new started PinActivity, after a screen rotation for instance
finishActivity(PinActivity.PIN_REQUEST_CODE)
}
else -> {
pinLocker.block()
moveTaskToBack(true)
if (pinLocker.getLiveState().value != PinLocker.State.UNLOCKED) {
// Remove the task, to be sure that PIN code will be requested when resumed
finishAndRemoveTask()
}
}
}
}

View File

@ -30,8 +30,6 @@ import androidx.fragment.app.Fragment
import im.vector.app.R
import timber.log.Timber
private const val LOG_TAG = "PermissionUtils"
// Android M permission request code management
private const val PERMISSIONS_GRANTED = true
private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
@ -42,16 +40,18 @@ const val PERMISSION_CAMERA = 0x1
private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
private const val PERMISSION_READ_EXTERNAL_STORAGE = 0x1 shl 4
// Permissions sets
const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA or PERMISSION_WRITE_EXTERNAL_STORAGE
const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
const val PERMISSIONS_FOR_READING_FILES = PERMISSION_READ_EXTERNAL_STORAGE
const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
@ -67,7 +67,6 @@ const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
const val PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT = 578
const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
/**
@ -79,6 +78,7 @@ fun logPermissionStatuses(context: Context) {
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_CONTACTS)
Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
@ -145,7 +145,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
fragment: Fragment?,
requestCode: Int,
@StringRes rationaleMessage: Int
): Boolean {
): Boolean {
var isPermissionGranted = false
// sanity check
@ -161,7 +161,8 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
&& PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap) {
&& PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap
&& PERMISSIONS_FOR_READING_FILES != permissionsToBeGrantedBitMap) {
Timber.w("## checkPermissions(): permissions to be granted are not supported")
isPermissionGranted = false
} else {
@ -188,6 +189,12 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
}
if (PERMISSION_READ_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_READ_EXTERNAL_STORAGE) {
val permissionType = Manifest.permission.READ_EXTERNAL_STORAGE
isRequestPermissionRequired = isRequestPermissionRequired or
updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
}
// the contact book access is requested for any android platforms
// for android M, we use the system preferences
// for android < M, we use a dedicated settings

View File

@ -1,39 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.core.utils
import androidx.annotation.ColorRes
import im.vector.app.R
import kotlin.math.abs
@ColorRes
fun getColorFromUserId(userId: String?): Int {
var hash = 0
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
return when (abs(hash) % 8) {
1 -> R.color.riotx_username_2
2 -> R.color.riotx_username_3
3 -> R.color.riotx_username_4
4 -> R.color.riotx_username_5
5 -> R.color.riotx_username_6
6 -> R.color.riotx_username_7
7 -> R.color.riotx_username_8
else -> R.color.riotx_username_1
}
}

View File

@ -42,7 +42,6 @@ import im.vector.app.core.extensions.getMeasurements
import im.vector.app.core.utils.PERMISSIONS_EMPTY
import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback
import kotlin.math.max
@ -215,10 +214,10 @@ class AttachmentTypeSelectorView(context: Context,
*/
enum class Type(val permissionsBit: Int) {
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
GALLERY(PERMISSIONS_FOR_WRITING_FILES),
FILE(PERMISSIONS_FOR_WRITING_FILES),
GALLERY(PERMISSIONS_EMPTY),
FILE(PERMISSIONS_EMPTY),
STICKER(PERMISSIONS_EMPTY),
AUDIO(PERMISSIONS_FOR_WRITING_FILES),
AUDIO(PERMISSIONS_EMPTY),
CONTACT(PERMISSIONS_FOR_PICKING_CONTACT)
}
}

View File

@ -42,17 +42,13 @@ import im.vector.app.core.extensions.cleanup
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.OnSnapPositionChangeListener
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT
import im.vector.app.core.utils.SnapOnScrollListener
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.attachSnapHelperWithListener
import im.vector.app.core.utils.checkPermissions
import im.vector.app.features.media.createUCropWithDefaultSettings
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_attachments_preview.*
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@ -102,7 +98,7 @@ class AttachmentsPreviewFragment @Inject constructor(
handleRemoveAction()
true
}
R.id.attachmentsPreviewEditAction -> {
R.id.attachmentsPreviewEditAction -> {
handleEditAction()
true
}
@ -183,22 +179,7 @@ class AttachmentsPreviewFragment @Inject constructor(
viewModel.handle(AttachmentsPreviewAction.RemoveCurrentAttachment)
}
private fun handleEditAction() {
// check permissions
if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT)) {
doHandleEditAction()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT && allGranted(grantResults)) {
doHandleEditAction()
}
}
private fun doHandleEditAction() = withState(viewModel) {
private fun handleEditAction() = withState(viewModel) {
val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState
val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}")
val uri = currentAttachment.queryUri

View File

@ -31,6 +31,7 @@ import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.ensureProtocol
import im.vector.app.features.discovery.change.SetIdentityServerFragment
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.terms.ReviewTermsActivity
import org.matrix.android.sdk.api.session.identity.SharedState
import org.matrix.android.sdk.api.session.identity.ThreePid
@ -178,10 +179,6 @@ class DiscoverySettingsFragment @Inject constructor(
}
private fun navigateToChangeIdentityServerFragment() {
parentFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom, R.anim.anim_slide_in_bottom, R.anim.anim_slide_out_bottom)
.replace(R.id.vector_settings_page, SetIdentityServerFragment::class.java, null)
.addToBackStack(null)
.commit()
(vectorBaseActivity as? VectorSettingsActivity)?.navigateTo(SetIdentityServerFragment::class.java)
}
}

View File

@ -16,13 +16,11 @@
package im.vector.app.features.home
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.annotation.AnyThread
import androidx.annotation.UiThread
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import com.amulyakhare.textdrawable.TextDrawable
import com.bumptech.glide.request.RequestOptions
@ -33,7 +31,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp
import im.vector.app.core.glide.GlideRequest
import im.vector.app.core.glide.GlideRequests
import im.vector.app.core.utils.getColorFromUserId
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.util.MatrixItem
@ -43,7 +41,8 @@ import javax.inject.Inject
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
*/
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
class AvatarRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val matrixItemColorProvider: MatrixItemColorProvider) {
companion object {
private const val THUMBNAIL_SIZE = 250
@ -51,8 +50,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
@UiThread
fun render(matrixItem: MatrixItem, imageView: ImageView) {
render(imageView.context,
GlideApp.with(imageView),
render(GlideApp.with(imageView),
matrixItem,
DrawableImageViewTarget(imageView))
}
@ -64,8 +62,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
@UiThread
fun render(matrixItem: MatrixItem, imageView: ImageView, glideRequests: GlideRequests) {
render(imageView.context,
glideRequests,
render(glideRequests,
matrixItem,
DrawableImageViewTarget(imageView))
}
@ -79,7 +76,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
displayName = mappedContact.displayName
)
val placeholder = getPlaceholderDrawable(imageView.context, matrixItem)
val placeholder = getPlaceholderDrawable(matrixItem)
GlideApp.with(imageView)
.load(mappedContact.photoURI)
.apply(RequestOptions.circleCropTransform())
@ -88,11 +85,10 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
}
@UiThread
fun render(context: Context,
glideRequests: GlideRequests,
fun render(glideRequests: GlideRequests,
matrixItem: MatrixItem,
target: Target<Drawable>) {
val placeholder = getPlaceholderDrawable(context, matrixItem)
val placeholder = getPlaceholderDrawable(matrixItem)
buildGlideRequest(glideRequests, matrixItem.avatarUrl)
.placeholder(placeholder)
.into(target)
@ -100,7 +96,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
@AnyThread
@Throws
fun shortcutDrawable(context: Context, glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
fun shortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
return glideRequests
.asBitmap()
.apply {
@ -108,7 +104,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
if (resolvedUrl != null) {
load(resolvedUrl)
} else {
val avatarColor = avatarColor(matrixItem, context)
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
load(TextDrawable.builder()
.beginConfig()
.bold()
@ -130,8 +126,8 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
}
@AnyThread
fun getPlaceholderDrawable(context: Context, matrixItem: MatrixItem): Drawable {
val avatarColor = avatarColor(matrixItem, context)
fun getPlaceholderDrawable(matrixItem: MatrixItem): Drawable {
val avatarColor = matrixItemColorProvider.getColor(matrixItem)
return TextDrawable.builder()
.beginConfig()
.bold()
@ -152,11 +148,4 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
return activeSessionHolder.getSafeActiveSession()?.contentUrlResolver()
?.resolveThumbnail(avatarUrl, THUMBNAIL_SIZE, THUMBNAIL_SIZE, ContentUrlResolver.ThumbnailMethod.SCALE)
}
private fun avatarColor(matrixItem: MatrixItem, context: Context): Int {
return when (matrixItem) {
is MatrixItem.UserItem -> ContextCompat.getColor(context, getColorFromUserId(matrixItem.id))
else -> ContextCompat.getColor(context, getColorFromRoomId(matrixItem.id))
}
}
}

View File

@ -70,7 +70,7 @@ class ShortcutsHandler @Inject constructor(
.map { room ->
val intent = RoomDetailActivity.shortcutIntent(context, room.roomId)
val bitmap = try {
avatarRenderer.shortcutDrawable(context, GlideApp.with(context), room.toMatrixItem(), iconSize)
avatarRenderer.shortcutDrawable(GlideApp.with(context), room.toMatrixItem(), iconSize)
} catch (failure: Throwable) {
null
}

View File

@ -97,7 +97,6 @@ import im.vector.app.core.utils.colorizeMatchingText
import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.getColorFromUserId
import im.vector.app.core.utils.isValidUrl
import im.vector.app.core.utils.onPermissionResultAudioIpCall
import im.vector.app.core.utils.onPermissionResultVideoIpCall
@ -127,6 +126,7 @@ import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem
@ -217,7 +217,9 @@ class RoomDetailFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val colorProvider: ColorProvider,
private val notificationUtils: NotificationUtils,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager) :
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
private val matrixItemColorProvider: MatrixItemColorProvider
) :
VectorBaseFragment(),
TimelineEventController.Callback,
VectorInviteView.Callback,
@ -790,7 +792,7 @@ class RoomDetailFragment @Inject constructor(
// switch to expanded bar
composerLayout.composerRelatedMessageTitle.apply {
text = event.senderInfo.disambiguatedDisplayName
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId)))
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
}
val messageContent: MessageContent? = event.getLastMessageContent()

View File

@ -19,18 +19,20 @@ package im.vector.app.features.home.room.detail.timeline
import androidx.annotation.ColorInt
import im.vector.app.R
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.getColorFromUserId
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject
class MessageColorProvider @Inject constructor(
private val colorProvider: ColorProvider,
private val matrixItemColorProvider: MatrixItemColorProvider,
private val vectorPreferences: VectorPreferences) {
@ColorInt
fun getMemberNameTextColor(userId: String): Int {
return colorProvider.getColor(getColorFromUserId(userId))
fun getMemberNameTextColor(matrixItem: MatrixItem): Int {
return matrixItemColorProvider.getColor(matrixItem)
}
@ColorInt

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.detail.timeline.helper
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.VisibleForTesting
import im.vector.app.R
import im.vector.app.core.resources.ColorProvider
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
@Singleton
class MatrixItemColorProvider @Inject constructor(
private val colorProvider: ColorProvider
) {
private val cache = mutableMapOf<String, Int>()
@ColorInt
fun getColor(matrixItem: MatrixItem): Int {
return cache.getOrPut(matrixItem.id) {
colorProvider.getColor(
when (matrixItem) {
is MatrixItem.UserItem -> getColorFromUserId(matrixItem.id)
else -> getColorFromRoomId(matrixItem.id)
}
)
}
}
companion object {
@ColorRes
@VisibleForTesting
fun getColorFromUserId(userId: String?): Int {
var hash = 0
userId?.toList()?.map { chr -> hash = (hash shl 5) - hash + chr.toInt() }
return when (abs(hash) % 8) {
1 -> R.color.riotx_username_2
2 -> R.color.riotx_username_3
3 -> R.color.riotx_username_4
4 -> R.color.riotx_username_5
5 -> R.color.riotx_username_6
6 -> R.color.riotx_username_7
7 -> R.color.riotx_username_8
else -> R.color.riotx_username_1
}
}
@ColorRes
private fun getColorFromRoomId(roomId: String?): Int {
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) {
1 -> R.color.riotx_avatar_fill_2
2 -> R.color.riotx_avatar_fill_3
else -> R.color.riotx_avatar_fill_1
}
}
}
}

View File

@ -21,13 +21,13 @@ package im.vector.app.features.home.room.detail.timeline.helper
import im.vector.app.core.date.DateFormatKind
import im.vector.app.core.date.VectorDateFormatter
import im.vector.app.core.extensions.localDateTime
import im.vector.app.core.resources.ColorProvider
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType
@ -49,7 +49,7 @@ import javax.inject.Inject
class MessageInformationDataFactory @Inject constructor(private val session: Session,
private val roomSummaryHolder: RoomSummaryHolder,
private val dateFormatter: VectorDateFormatter,
private val colorProvider: ColorProvider) {
private val vectorPreferences: VectorPreferences) {
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
// Non nullability has been tested before
@ -81,6 +81,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
avatarUrl = event.senderInfo.avatarUrl,
memberName = event.senderInfo.disambiguatedDisplayName,
showInformation = showInformation,
forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
orderedReactionList = event.annotations?.reactionsSummary
// ?.filter { isSingleEmoji(it.key) }
?.map {

View File

@ -21,6 +21,8 @@ import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.IdRes
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.airbnb.epoxy.EpoxyAttribute
import im.vector.app.R
import im.vector.app.core.utils.DebouncedClickListener
@ -69,8 +71,14 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
holder.avatarImageView.setOnClickListener(null)
holder.memberNameView.setOnClickListener(null)
holder.avatarImageView.visibility = View.GONE
holder.memberNameView.visibility = View.GONE
holder.timeView.visibility = View.GONE
if (attributes.informationData.forceShowTimestamp) {
holder.memberNameView.isInvisible = true
holder.timeView.isVisible = true
holder.timeView.text = attributes.informationData.time
} else {
holder.memberNameView.isVisible = false
holder.timeView.isVisible = false
}
holder.avatarImageView.setOnLongClickListener(null)
holder.memberNameView.setOnLongClickListener(null)
}
@ -85,7 +93,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
super.unbind(holder)
}
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.senderId)
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.matrixItem)
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)

View File

@ -32,6 +32,7 @@ data class MessageInformationData(
val avatarUrl: String?,
val memberName: CharSequence? = null,
val showInformation: Boolean = true,
val forceShowTimestamp: Boolean = false,
/*List of reactions (emoji,count,isSelected)*/
val orderedReactionList: List<ReactionInfoData>? = null,
val pollResponseAggregatedSummary: PollResponseData? = null,

View File

@ -53,7 +53,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
@UiThread
fun bind(textView: TextView) {
tv = WeakReference(textView)
avatarRenderer.render(context, glideRequests, matrixItem, target)
avatarRenderer.render(glideRequests, matrixItem, target)
}
// ReplacementSpan *****************************************************************************
@ -99,7 +99,7 @@ class PillImageSpan(private val glideRequests: GlideRequests,
val icon = try {
avatarRenderer.getCachedDrawable(glideRequests, matrixItem)
} catch (exception: Exception) {
avatarRenderer.getPlaceholderDrawable(context, matrixItem)
avatarRenderer.getPlaceholderDrawable(matrixItem)
}
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {

View File

@ -19,10 +19,12 @@ package im.vector.app.features.login
import android.content.Context
import android.content.Intent
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
@ -42,10 +44,10 @@ import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.login.terms.toLocalizedLoginTerms
import im.vector.app.features.pin.UnlockedActivity
import kotlinx.android.synthetic.main.activity_login.*
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.extensions.tryOrNull
import kotlinx.android.synthetic.main.activity_login.*
import javax.inject.Inject
/**
@ -72,6 +74,13 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
get() = supportFragmentManager.findFragmentById(R.id.loginFragmentContainer)
private val commonOption: (FragmentTransaction) -> Unit = { ft ->
// Find the loginLogo on the current Fragment, this should not return null
(topFragment?.view as? ViewGroup)
// Find findViewById does not work, I do not know why
// findViewById<View?>(R.id.loginLogo)
?.children
?.firstOrNull { it.id == R.id.loginLogo }
?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
}
@ -127,7 +136,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
is LoginViewEvents.OutdatedHomeserver -> {
AlertDialog.Builder(this)
.setTitle(R.string.login_error_outdated_homeserver_title)
.setMessage(R.string.login_error_outdated_homeserver_content)
.setMessage(R.string.login_error_outdated_homeserver_warning_content)
.setPositiveButton(R.string.ok, null)
.show()
Unit
@ -136,6 +145,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedAc
addFragmentToBackstack(R.id.loginFragmentContainer,
LoginServerSelectionFragment::class.java,
option = { ft ->
findViewById<View?>(R.id.loginSplashLogo)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
findViewById<View?>(R.id.loginSplashTitle)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
findViewById<View?>(R.id.loginSplashSubmit)?.let { ft.addSharedElement(it, ViewCompat.getTransitionName(it) ?: "") }
// TODO Disabled because it provokes a flickering

View File

@ -748,34 +748,21 @@ class LoginViewModel @AssistedInject constructor(
else -> LoginMode.Unsupported
}
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
notSupported()
} else {
// FIXME We should post a view event here normally?
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
)
}
// FIXME We should post a view event here normally?
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized,
homeServerUrl = data.homeServerUrl,
loginMode = loginMode,
loginModeSupportedTypes = data.supportedLoginTypes.toList()
)
}
if ((loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported)
|| data.isOutdatedHomeserver) {
// Notify the UI
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
}
}
is LoginFlowResult.OutdatedHomeserver -> {
notSupported()
}
}
}
private fun notSupported() {
// Notify the UI
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
setState {
copy(
asyncHomeServerLoginFlowRequest = Uninitialized
)
}
}
})

View File

@ -27,9 +27,9 @@ import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.settings.VectorPreferences
import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import me.gujun.android.span.span
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
@ -72,6 +72,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
private val currentSession: Session?
get() = activeSessionDataSource.currentValue?.orNull()
private var useCompleteNotificationFormat = vectorPreferences.useCompleteNotificationFormat()
/**
Should be called as soon as a new event is ready to be displayed.
The notification corresponding to this event will not be displayed until
@ -243,8 +245,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
roomEvents.add(event)
}
}
is InviteNotifiableEvent -> invitationEvents.add(event)
is SimpleNotifiableEvent -> simpleEvents.add(event)
is InviteNotifiableEvent -> invitationEvents.add(event)
is SimpleNotifiableEvent -> simpleEvents.add(event)
else -> Timber.w("Type not handled")
}
}
@ -253,6 +255,16 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
var globalLastMessageTimestamp = 0L
val newSettings = vectorPreferences.useCompleteNotificationFormat()
if (newSettings != useCompleteNotificationFormat) {
// Settings has changed, remove all current notifications
notificationUtils.cancelAllNotifications()
useCompleteNotificationFormat = newSettings
}
var simpleNotificationRoomCounter = 0
var simpleNotificationMessageCounter = 0
// events have been grouped by roomId
for ((roomId, events) in roomIdToEventMap) {
// Build the notification for the room
@ -263,6 +275,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
continue
}
simpleNotificationRoomCounter++
val roomName = events[0].roomName ?: events[0].senderName ?: ""
val roomEventGroupInfo = RoomEventGroupInfo(
@ -303,6 +316,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
roomEventGroupInfo.hasSmartReplyError = true
} else {
if (!event.isRedacted) {
simpleNotificationMessageCounter++
style.addMessage(event.body, event.timestamp, senderPerson)
}
}
@ -361,16 +375,18 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
stringProvider.getString(R.string.notification_ticker_text_group, roomName, events.last().senderName, events.last().description)
}
val notification = notificationUtils.buildMessagesListNotification(
style,
roomEventGroupInfo,
largeBitmap,
lastMessageTimestamp,
myUserDisplayName,
tickerText)
if (useCompleteNotificationFormat) {
val notification = notificationUtils.buildMessagesListNotification(
style,
roomEventGroupInfo,
largeBitmap,
lastMessageTimestamp,
myUserDisplayName,
tickerText)
// is there an id for this room?
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
// is there an id for this room?
notificationUtils.showNotificationMessage(roomId, ROOM_MESSAGES_NOTIFICATION_ID, notification)
}
hasNewEvent = true
summaryIsNoisy = summaryIsNoisy || roomEventGroupInfo.shouldBing
@ -383,8 +399,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
for (event in invitationEvents) {
// We build a invitation notification
if (firstTime || !event.hasBeenDisplayed) {
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
if (useCompleteNotificationFormat) {
val notification = notificationUtils.buildRoomInvitationNotification(event, session.myUserId)
notificationUtils.showNotificationMessage(event.roomId, ROOM_INVITATION_NOTIFICATION_ID, notification)
}
event.hasBeenDisplayed = true // we can consider it as displayed
hasNewEvent = true
summaryIsNoisy = summaryIsNoisy || event.noisy
@ -396,8 +414,10 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
for (event in simpleEvents) {
// We build a simple notification
if (firstTime || !event.hasBeenDisplayed) {
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
if (useCompleteNotificationFormat) {
val notification = notificationUtils.buildSimpleEventNotification(event, session.myUserId)
notificationUtils.showNotificationMessage(event.eventId, ROOM_EVENT_NOTIFICATION_ID, notification)
}
event.hasBeenDisplayed = true // we can consider it as displayed
hasNewEvent = true
summaryIsNoisy = summaryIsNoisy || event.noisy
@ -421,19 +441,76 @@ class NotificationDrawerManager @Inject constructor(private val context: Context
if (eventList.isEmpty() || eventList.all { it.isRedacted }) {
notificationUtils.cancelNotificationMessage(null, SUMMARY_NOTIFICATION_ID)
} else {
// FIXME roomIdToEventMap.size is not correct, this is the number of rooms
val nbEvents = roomIdToEventMap.size + simpleEvents.size
val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents)
summaryInboxStyle.setBigContentTitle(sumTitle)
// TODO get latest event?
.setSummaryText(stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages, nbEvents, nbEvents))
val notification = notificationUtils.buildSummaryListNotification(
summaryInboxStyle,
sumTitle,
noisy = hasNewEvent && summaryIsNoisy,
lastMessageTimestamp = globalLastMessageTimestamp)
if (useCompleteNotificationFormat) {
val notification = notificationUtils.buildSummaryListNotification(
summaryInboxStyle,
sumTitle,
noisy = hasNewEvent && summaryIsNoisy,
lastMessageTimestamp = globalLastMessageTimestamp)
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
} else {
// Add the simple events as message (?)
simpleNotificationMessageCounter += simpleEvents.size
val numberOfInvitations = invitationEvents.size
val privacyTitle = if (numberOfInvitations > 0) {
val invitationsStr = stringProvider.getQuantityString(R.plurals.notification_invitations, numberOfInvitations, numberOfInvitations)
if (simpleNotificationMessageCounter > 0) {
// Invitation and message
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
if (simpleNotificationRoomCounter > 1) {
// In several rooms
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
stringProvider.getString(
R.string.notification_unread_notified_messages_in_room_and_invitation,
messageStr,
roomStr,
invitationsStr
)
} else {
// In one room
stringProvider.getString(
R.string.notification_unread_notified_messages_and_invitation,
messageStr,
invitationsStr
)
}
} else {
// Only invitation
invitationsStr
}
} else {
// No invitation, only messages
val messageStr = stringProvider.getQuantityString(R.plurals.room_new_messages_notification,
simpleNotificationMessageCounter, simpleNotificationMessageCounter)
if (simpleNotificationRoomCounter > 1) {
// In several rooms
val roomStr = stringProvider.getQuantityString(R.plurals.notification_unread_notified_messages_in_room_rooms,
simpleNotificationRoomCounter, simpleNotificationRoomCounter)
stringProvider.getString(R.string.notification_unread_notified_messages_in_room, messageStr, roomStr)
} else {
// In one room
messageStr
}
}
val notification = notificationUtils.buildSummaryListNotification(
style = null,
compatSummary = privacyTitle,
noisy = hasNewEvent && summaryIsNoisy,
lastMessageTimestamp = globalLastMessageTimestamp)
notificationUtils.showNotificationMessage(null, SUMMARY_NOTIFICATION_ID, notification)
}
if (hasNewEvent && summaryIsNoisy) {
try {

View File

@ -772,7 +772,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
/**
* Build the summary notification
*/
fun buildSummaryListNotification(style: NotificationCompat.InboxStyle,
fun buildSummaryListNotification(style: NotificationCompat.InboxStyle?,
compatSummary: String,
noisy: Boolean,
lastMessageTimestamp: Long): Notification {

View File

@ -28,7 +28,6 @@ import im.vector.app.core.platform.VectorBaseActivity
class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
companion object {
const val PIN_REQUEST_CODE = 17890
fun newIntent(context: Context, args: PinArgs): Intent {

View File

@ -32,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.settings.VectorPreferences
import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -42,7 +43,8 @@ data class PinArgs(
) : Parcelable
class PinFragment @Inject constructor(
private val pinCodeStore: PinCodeStore
private val pinCodeStore: PinCodeStore,
private val vectorPreferences: VectorPreferences
) : VectorBaseFragment() {
private val fragmentArgs: PinArgs by args()
@ -53,54 +55,10 @@ class PinFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState)
when (fragmentArgs.pinMode) {
PinMode.CREATE -> showCreateFragment()
PinMode.DELETE -> showDeleteFragment()
PinMode.AUTH -> showAuthFragment()
}
}
private fun showDeleteFragment() {
val encodedPin = pinCodeStore.getEncodedPin() ?: return
val authFragment = PFLockScreenFragment()
val builder = PFFLockScreenConfiguration.Builder(requireContext())
.setUseBiometric(pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0)
.setTitle(getString(R.string.auth_pin_confirm_to_disable_title))
.setClearCodeOnError(true)
.setMode(PFFLockScreenConfiguration.MODE_AUTH)
authFragment.setConfiguration(builder.build())
authFragment.setEncodedPinCode(encodedPin)
authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener {
override fun onPinLoginFailed() {
onWrongPin()
}
override fun onBiometricAuthSuccessful() {
lifecycleScope.launch {
pinCodeStore.deleteEncodedPin()
vectorBaseActivity.setResult(Activity.RESULT_OK)
vectorBaseActivity.finish()
}
}
override fun onBiometricAuthLoginFailed() {
val remainingAttempts = pinCodeStore.onWrongBiometrics()
if (remainingAttempts <= 0) {
// Disable Biometrics
builder.setUseBiometric(false)
authFragment.setConfiguration(builder.build())
}
}
override fun onCodeInputSuccessful() {
lifecycleScope.launch {
pinCodeStore.deleteEncodedPin()
vectorBaseActivity.setResult(Activity.RESULT_OK)
vectorBaseActivity.finish()
}
}
})
replaceFragment(R.id.pinFragmentContainer, authFragment)
}
private fun showCreateFragment() {
val createFragment = PFLockScreenFragment()
val builder = PFFLockScreenConfiguration.Builder(requireContext())
@ -131,9 +89,8 @@ class PinFragment @Inject constructor(
val authFragment = PFLockScreenFragment()
val canUseBiometrics = pinCodeStore.getRemainingBiometricsAttemptsNumber() > 0
val builder = PFFLockScreenConfiguration.Builder(requireContext())
.setUseBiometric(true)
.setAutoShowBiometric(true)
.setUseBiometric(canUseBiometrics)
.setUseBiometric(vectorPreferences.useBiometricsToUnlock() && canUseBiometrics)
.setAutoShowBiometric(canUseBiometrics)
.setTitle(getString(R.string.auth_pin_title))
.setLeftButton(getString(R.string.auth_pin_forgot))

View File

@ -22,12 +22,14 @@ import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.OnLifecycleEvent
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
// 2 minutes, when enabled
private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
/**
@ -35,24 +37,22 @@ private const val PERIOD_OF_GRACE_IN_MS = 2 * 60 * 1000L
* It automatically locks when entering background/foreground with a grace period.
* You can force to unlock with unlock method, use it whenever the pin code has been validated.
*/
@Singleton
class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : LifecycleObserver {
class PinLocker @Inject constructor(
private val pinCodeStore: PinCodeStore,
private val vectorPreferences: VectorPreferences
) : LifecycleObserver {
enum class State {
// App is locked, can be unlock
LOCKED,
// App is blocked and can't be unlocked as long as the app is in foreground
BLOCKED,
// is unlocked, the app can be used
// App is unlocked, the app can be used
UNLOCKED
}
private val liveState = MutableLiveData<State>()
private var isBlocked = false
private var shouldBeLocked = true
private var entersBackgroundTs = 0L
@ -62,13 +62,13 @@ class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : Li
private fun computeState() {
GlobalScope.launch {
val state = if (isBlocked) {
State.BLOCKED
} else if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
val state = if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
State.LOCKED
} else {
State.UNLOCKED
}
.also { Timber.v("New state: $it") }
if (liveState.value != state) {
liveState.postValue(state)
}
@ -81,23 +81,25 @@ class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : Li
computeState()
}
fun block() {
Timber.v("Block app")
isBlocked = true
computeState()
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= PERIOD_OF_GRACE_IN_MS
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background")
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= getGracePeriod()
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background shouldBeLocked: $shouldBeLocked")
computeState()
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun entersBackground() {
isBlocked = false
Timber.v("App enters background")
entersBackgroundTs = SystemClock.elapsedRealtime()
}
private fun getGracePeriod(): Long {
return if (vectorPreferences.useGracePeriod()) {
PERIOD_OF_GRACE_IN_MS
} else {
0L
}
}
}

View File

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

View File

@ -17,6 +17,7 @@
package im.vector.app.features.raw.wellknown
import com.squareup.moshi.JsonAdapter
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.di.MoshiProvider
object ElementWellKnownMapper {
@ -24,6 +25,6 @@ object ElementWellKnownMapper {
val adapter: JsonAdapter<ElementWellKnown> = MoshiProvider.providesMoshi().adapter(ElementWellKnown::class.java)
fun from(value: String): ElementWellKnown? {
return adapter.fromJson(value)
return tryOrNull("Unable to parse well-known data") { adapter.fromJson(value) }
}
}

View File

@ -166,6 +166,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
// Security
const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE"
const val SETTINGS_SECURITY_USE_PIN_CODE_FLAG = "SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
private const val SETTINGS_SECURITY_USE_BIOMETRICS_FLAG = "SETTINGS_SECURITY_USE_BIOMETRICS_FLAG"
private const val SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG = "SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG"
const val SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG = "SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG"
// other
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
@ -839,12 +842,29 @@ class VectorPreferences @Inject constructor(private val context: Context) {
}
/**
* The user enable protecting app access with pin code
* The user enable protecting app access with pin code.
* Currently we use the pin code store to know if the pin is enabled, so this is not used
*/
fun useFlagPinCode(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_PIN_CODE_FLAG, false)
}
fun useBiometricsToUnlock(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_BIOMETRICS_FLAG, true)
}
fun useGracePeriod(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG, true)
}
/**
* Return true if Pin code is disabled, or if user set the settings to see full notification content
*/
fun useCompleteNotificationFormat(): Boolean {
return !useFlagPinCode()
|| defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG, true)
}
fun backgroundSyncTimeOut(): Int {
return tryOrNull {
// The xml pref is saved as a string so use getString and parse

View File

@ -17,6 +17,7 @@ package im.vector.app.features.settings
import android.content.Context
import android.content.Intent
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
@ -134,6 +135,14 @@ class VectorSettingsActivity : VectorBaseActivity(),
}
}
fun <T: Fragment> navigateTo(fragmentClass: Class<T>) {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.right_in, R.anim.fade_out, R.anim.fade_in, R.anim.right_out)
.replace(R.id.vector_settings_page, fragmentClass, null)
.addToBackStack(null)
.commit()
}
companion object {
fun getIntent(context: Context, directAccess: Int) = Intent(context, VectorSettingsActivity::class.java)
.apply { putExtra(EXTRA_DIRECT_ACCESS, directAccess) }

View File

@ -0,0 +1,83 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.settings
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import im.vector.app.R
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinMode
import kotlinx.coroutines.launch
import javax.inject.Inject
class VectorSettingsPinFragment @Inject constructor(
private val pinCodeStore: PinCodeStore,
private val navigator: Navigator,
private val notificationDrawerManager: NotificationDrawerManager
) : VectorSettingsBaseFragment() {
override var titleRes = R.string.settings_security_application_protection_screen_title
override val preferenceXmlRes = R.xml.vector_settings_pin
private val usePinCodePref by lazy {
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!!
}
private val useCompleteNotificationPref by lazy {
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG)!!
}
override fun bindPref() {
refreshPinCodeStatus()
useCompleteNotificationPref.setOnPreferenceChangeListener { _, _ ->
// Refresh the drawer for an immediate effect of this change
notificationDrawerManager.refreshNotificationDrawer()
true
}
}
private fun refreshPinCodeStatus() {
lifecycleScope.launchWhenResumed {
val hasPinCode = pinCodeStore.hasEncodedPin()
usePinCodePref.isChecked = hasPinCode
usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (hasPinCode) {
lifecycleScope.launch {
pinCodeStore.deleteEncodedPin()
refreshPinCodeStatus()
}
} else {
navigator.openPinCode(this@VectorSettingsPinFragment, PinMode.CREATE)
}
true
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
refreshPinCodeStatus()
}
}
}

View File

@ -56,9 +56,8 @@ import im.vector.app.features.crypto.recover.BootstrapBottomSheet
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.pin.PinActivity
import im.vector.app.features.pin.PinCodeStore
import im.vector.app.features.pin.PinLocker
import im.vector.app.features.pin.PinMode
import im.vector.app.features.raw.wellknown.ElementWellKnownMapper
import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isE2EByDefault
import im.vector.app.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers
@ -76,7 +75,6 @@ import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val pinLocker: PinLocker,
private val activeSessionHolder: ActiveSessionHolder,
private val pinCodeStore: PinCodeStore,
private val navigator: Navigator
@ -128,8 +126,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!!
}
private val usePinCodePref by lazy {
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!!
private val openPinCodeSettingsPref by lazy {
findPreference<VectorPreference>("SETTINGS_SECURITY_PIN")!!
}
override fun onCreateRecyclerView(inflater: LayoutInflater?, parent: ViewGroup?, savedInstanceState: Bundle?): RecyclerView {
@ -155,14 +153,13 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
disposables.add(it)
}
vectorActivity.getVectorComponent()
.rawService()
.getWellknown(session.myUserId, object : MatrixCallback<String> {
override fun onSuccess(data: String) {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
ElementWellKnownMapper.from(data)?.isE2EByDefault() == false
}
})
lifecycleScope.launchWhenResumed {
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible =
vectorActivity.getVectorComponent()
.rawService()
.getElementWellknown(session.myUserId)
?.isE2EByDefault() == false
}
}
private val secureBackupCategory by lazy {
@ -265,7 +262,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
}
refreshPinCodeStatus()
openPinCodeSettingsPref.setOnPreferenceClickListener {
openPinCodePreferenceScreen()
true
}
refreshXSigningStatus()
@ -321,62 +321,64 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_SAVE_MEGOLM_EXPORT) {
val uri = data?.data
if (resultCode == Activity.RESULT_OK && uri != null) {
activity?.let { activity ->
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
displayLoadingView()
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_SAVE_MEGOLM_EXPORT -> {
val uri = data?.data
if (uri != null) {
activity?.let { activity ->
ExportKeysDialog().show(activity, object : ExportKeysDialog.ExportKeyDialogListener {
override fun onPassphrase(passphrase: String) {
displayLoadingView()
KeysExporter(session)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
KeysExporter(session)
.export(requireContext(),
passphrase,
uri,
object : MatrixCallback<Boolean> {
override fun onSuccess(data: Boolean) {
if (data) {
requireActivity().toast(getString(R.string.encryption_exported_successfully))
} else {
requireActivity().toast(getString(R.string.unexpected_error))
}
hideLoadingView()
}
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
override fun onFailure(failure: Throwable) {
onCommonDone(failure.localizedMessage)
}
})
}
})
}
})
}
}
PinActivity.PIN_REQUEST_CODE -> {
doOpenPinCodePreferenceScreen()
}
REQUEST_E2E_FILE_REQUEST_CODE -> {
importKeys(data)
}
}
} else if (requestCode == PinActivity.PIN_REQUEST_CODE) {
pinLocker.unlock()
refreshPinCodeStatus()
} else if (requestCode == REQUEST_E2E_FILE_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
importKeys(data)
}
}
}
private fun refreshPinCodeStatus() {
private fun openPinCodePreferenceScreen() {
lifecycleScope.launchWhenResumed {
val hasPinCode = pinCodeStore.hasEncodedPin()
usePinCodePref.isChecked = hasPinCode
usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
val pinMode = if (hasPinCode) {
PinMode.DELETE
} else {
PinMode.CREATE
}
navigator.openPinCode(this@VectorSettingsSecurityPrivacyFragment, pinMode)
true
if (hasPinCode) {
navigator.openPinCode(this@VectorSettingsSecurityPrivacyFragment, PinMode.AUTH)
} else {
doOpenPinCodePreferenceScreen()
}
}
}
private fun doOpenPinCodePreferenceScreen() {
(vectorActivity as? VectorSettingsActivity)?.navigateTo(VectorSettingsPinFragment::class.java)
}
private fun refreshKeysManagementSection() {
// If crypto is not enabled parent section will be removed
// TODO notice that this will not work when no network

View File

@ -28,23 +28,19 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES
import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_PICK_ATTACHMENT
import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.features.attachments.AttachmentsHelper
import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.app.features.login.LoginActivity
import kotlinx.android.synthetic.main.fragment_incoming_share.*
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject
/**
@ -76,7 +72,7 @@ class IncomingShareFragment @Inject constructor(
val intent = vectorBaseActivity.intent
val isShareManaged = when (intent?.action) {
Intent.ACTION_SEND -> {
Intent.ACTION_SEND -> {
var isShareManaged = attachmentsHelper.handleShareIntent(requireContext(), intent)
if (!isShareManaged) {
isShareManaged = handleTextShare(intent)
@ -106,7 +102,7 @@ class IncomingShareFragment @Inject constructor(
}
viewModel.observeViewEvents {
when (it) {
is IncomingShareViewEvents.ShareToRoom -> handleShareToRoom(it)
is IncomingShareViewEvents.ShareToRoom -> handleShareToRoom(it)
is IncomingShareViewEvents.EditMediaBeforeSending -> handleEditMediaBeforeSending(it)
is IncomingShareViewEvents.MultipleRoomsShareDone -> handleMultipleRoomsShareDone(it)
}.exhaustive
@ -139,22 +135,6 @@ class IncomingShareFragment @Inject constructor(
}
}
override fun onResume() {
super.onResume()
// We need the read file permission
checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, PERMISSION_REQUEST_CODE_PICK_ATTACHMENT)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSION_REQUEST_CODE_PICK_ATTACHMENT && !allGranted(grantResults)) {
// Permission is mandatory
cannotManageShare(R.string.missing_permissions_error)
}
}
private fun handleShareToRoom(event: IncomingShareViewEvents.ShareToRoom) {
if (event.showAlert) {
showConfirmationDialog(event.roomSummary, event.sharedData)

View File

@ -28,6 +28,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.login.LoginMode
import im.vector.app.features.signout.soft.epoxy.loginCenterButtonItem
import im.vector.app.features.signout.soft.epoxy.loginErrorWithRetryItem
import im.vector.app.features.signout.soft.epoxy.loginHeaderItem
import im.vector.app.features.signout.soft.epoxy.loginPasswordFormItem
import im.vector.app.features.signout.soft.epoxy.loginRedButtonItem
import im.vector.app.features.signout.soft.epoxy.loginTextItem
@ -64,6 +65,9 @@ class SoftLogoutController @Inject constructor(
}
private fun buildHeader(state: SoftLogoutViewState) {
loginHeaderItem {
id("header")
}
loginTitleItem {
id("title")
text(stringProvider.getString(R.string.soft_logout_title))

View File

@ -25,16 +25,16 @@ import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.hasUnsavedKeys
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.login.LoginMode
import timber.log.Timber
/**
@ -102,7 +102,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
override fun onSuccess(data: LoginFlowResult) {
when (data) {
is LoginFlowResult.Success -> {
is LoginFlowResult.Success -> {
val loginMode = when {
// SSO login is taken first
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
@ -110,29 +110,12 @@ class SoftLogoutViewModel @AssistedInject constructor(
else -> LoginMode.Unsupported
}
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
notSupported()
} else {
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
}
setState {
copy(
asyncHomeServerLoginFlowRequest = Success(loginMode)
)
}
}
is LoginFlowResult.OutdatedHomeserver -> {
notSupported()
}
}
}
private fun notSupported() {
// Should not happen since it's a re-logout
// Notify the UI
setState {
copy(
asyncHomeServerLoginFlowRequest = Fail(IllegalStateException("Should not happen"))
)
}
}
})

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,16 +14,14 @@
* limitations under the License.
*/
package im.vector.app.features.home
package im.vector.app.features.signout.soft.epoxy
import androidx.annotation.ColorRes
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
@ColorRes
fun getColorFromRoomId(roomId: String?): Int {
return when ((roomId?.toList()?.sumBy { it.toInt() } ?: 0) % 3) {
1 -> R.color.riotx_avatar_fill_2
2 -> R.color.riotx_avatar_fill_3
else -> R.color.riotx_avatar_fill_1
}
@EpoxyModelClass(layout = R.layout.item_login_header)
abstract class LoginHeaderItem : VectorEpoxyModel<LoginHeaderItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View File

@ -13,7 +13,7 @@ Az Element-tel azért válik mindez lehetővé, mert a Matrix hálózatra épül
Az Element a te kezedbe adja az irányítást azáltal, hogy eldöntheted, ki tárolja a beszélgetéseidet. Az Element alkalmazásból több féle szolgáltatót is választhatsz:
1. Regisztrálhatsz ingyen egy fiókot a matrix.org nyilvános szerveren
1. Regisztrálhatsz ingyen egy fiókot a matrix.org nyilvános szerveren, amit a Matrix fejlesztői üzemeltetnek, vagy választhatsz a több ezer, ingyenes szerver közül, amit önkéntesek üzemeltetnek
2. A saját számítógépeden is futtathatsz szervert
3. Előfizethetsz egy saját szerverre az Element Matrix Szolgáltatások platformon

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="155dp"
android:height="33dp"
android:viewportWidth="155"
android:viewportHeight="33">
<path
android:pathData="M21.533,22.855H4.969C5.165,24.595 5.794,25.985 6.856,27.023C7.918,28.034 9.316,28.539 11.05,28.539C12.196,28.539 13.23,28.258 14.153,27.697C15.075,27.135 15.732,26.378 16.124,25.423H21.156C20.485,27.641 19.227,29.437 17.382,30.812C15.564,32.16 13.412,32.833 10.924,32.833C7.681,32.833 5.053,31.753 3.04,29.591C1.055,27.43 0.063,24.694 0.063,21.381C0.063,18.153 1.069,15.445 3.082,13.255C5.095,11.066 7.695,9.972 10.882,9.972C14.069,9.972 16.641,11.052 18.598,13.213C20.583,15.347 21.575,18.041 21.575,21.297L21.533,22.855ZM10.882,14.056C9.316,14.056 8.016,14.519 6.982,15.445C5.947,16.371 5.304,17.606 5.053,19.15H16.627C16.403,17.606 15.788,16.371 14.782,15.445C13.775,14.519 12.475,14.056 10.882,14.056Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M25.009,25.802V0.751H30V25.886C30,27.009 30.615,27.57 31.845,27.57L32.725,27.528V32.286C32.25,32.37 31.747,32.412 31.216,32.412C29.063,32.412 27.483,31.865 26.477,30.77C25.499,29.676 25.009,28.02 25.009,25.802Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M55.966,22.855H39.401C39.597,24.595 40.226,25.985 41.289,27.023C42.351,28.034 43.749,28.539 45.482,28.539C46.628,28.539 47.663,28.258 48.585,27.697C49.508,27.135 50.165,26.378 50.556,25.423H55.588C54.917,27.641 53.659,29.437 51.814,30.812C49.997,32.16 47.844,32.833 45.356,32.833C42.113,32.833 39.485,31.753 37.472,29.591C35.487,27.43 34.495,24.694 34.495,21.381C34.495,18.153 35.501,15.445 37.514,13.255C39.527,11.066 42.127,9.972 45.314,9.972C48.501,9.972 51.073,11.052 53.03,13.213C55.015,15.347 56.008,18.041 56.008,21.297L55.966,22.855ZM45.314,14.056C43.749,14.056 42.449,14.519 41.414,15.445C40.38,16.371 39.737,17.606 39.485,19.15H51.059C50.836,17.606 50.221,16.371 49.214,15.445C48.208,14.519 46.908,14.056 45.314,14.056Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M78.48,19.781V32.328H73.49V19.234C73.49,15.922 72.12,14.266 69.38,14.266C67.899,14.266 66.711,14.743 65.816,15.698C64.949,16.652 64.516,17.957 64.516,19.613V32.328H59.526V10.477H64.138V13.382C64.67,12.399 65.48,11.585 66.571,10.94C67.661,10.294 69.017,9.972 70.638,9.972C73.658,9.972 75.838,11.122 77.18,13.424C79.025,11.122 81.486,9.972 84.561,9.972C87.105,9.972 89.062,10.771 90.432,12.371C91.802,13.943 92.487,16.02 92.487,18.603V32.328H87.496V19.234C87.496,15.922 86.126,14.266 83.387,14.266C81.877,14.266 80.675,14.757 79.78,15.74C78.914,16.694 78.48,18.041 78.48,19.781Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M117.304,22.855H100.739C100.935,24.595 101.564,25.985 102.627,27.023C103.689,28.034 105.087,28.539 106.82,28.539C107.966,28.539 109.001,28.258 109.923,27.697C110.846,27.135 111.503,26.378 111.894,25.423H116.926C116.255,27.641 114.997,29.437 113.152,30.812C111.335,32.16 109.182,32.833 106.694,32.833C103.451,32.833 100.823,31.753 98.811,29.591C96.826,27.43 95.833,24.694 95.833,21.381C95.833,18.153 96.84,15.445 98.852,13.255C100.865,11.066 103.465,9.972 106.652,9.972C109.839,9.972 112.411,11.052 114.368,13.213C116.353,15.347 117.346,18.041 117.346,21.297L117.304,22.855ZM106.652,14.056C105.087,14.056 103.787,14.519 102.752,15.445C101.718,16.371 101.075,17.606 100.823,19.15H112.397C112.174,17.606 111.559,16.371 110.552,15.445C109.546,14.519 108.246,14.056 106.652,14.056Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M125.477,10.477V13.382C125.98,12.427 126.804,11.628 127.951,10.982C129.125,10.308 130.537,9.972 132.186,9.972C134.758,9.972 136.743,10.757 138.141,12.329C139.567,13.901 140.28,15.992 140.28,18.603V32.328H135.289V19.234C135.289,17.69 134.926,16.483 134.199,15.613C133.5,14.715 132.424,14.266 130.97,14.266C129.376,14.266 128.118,14.743 127.196,15.698C126.301,16.652 125.854,17.971 125.854,19.655V32.328H120.864V10.477H125.477Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M154.854,27.865V32.202C154.239,32.37 153.372,32.454 152.254,32.454C148.004,32.454 145.88,30.307 145.88,26.013V14.476H142.567V10.477H145.88V4.793H150.87V10.477H154.938V14.476H150.87V25.507C150.87,27.22 151.681,28.076 153.302,28.076L154.854,27.865Z"
android:fillColor="#ffffff"/>
</vector>

View File

@ -1,43 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="369dp"
android:height="211dp"
android:viewportWidth="369"
android:viewportHeight="211">
<path
android:pathData="M51.155,186.5H11.69C12.156,190.633 13.655,193.933 16.186,196.4C18.717,198.8 22.047,200 26.177,200C28.908,200 31.373,199.333 33.571,198C35.769,196.667 37.334,194.867 38.267,192.6H50.256C48.658,197.867 45.66,202.133 41.264,205.4C36.934,208.6 31.806,210.2 25.877,210.2C18.151,210.2 11.89,207.633 7.094,202.5C2.365,197.367 0,190.867 0,183C0,175.333 2.398,168.9 7.194,163.7C11.99,158.5 18.184,155.9 25.778,155.9C33.371,155.9 39.499,158.467 44.161,163.6C48.891,168.667 51.255,175.067 51.255,182.8L51.155,186.5ZM25.778,165.6C22.047,165.6 18.95,166.7 16.486,168.9C14.021,171.1 12.489,174.033 11.89,177.7H39.466C38.933,174.033 37.467,171.1 35.069,168.9C32.672,166.7 29.574,165.6 25.778,165.6Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M59.437,193.5V134H71.327V193.7C71.327,196.367 72.792,197.7 75.723,197.7L77.821,197.6V208.9C76.689,209.1 75.49,209.2 74.224,209.2C69.095,209.2 65.332,207.9 62.934,205.3C60.603,202.7 59.437,198.767 59.437,193.5Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M133.193,186.5H93.728C94.194,190.633 95.693,193.933 98.224,196.4C100.755,198.8 104.085,200 108.215,200C110.946,200 113.41,199.333 115.609,198C117.807,196.667 119.372,194.867 120.304,192.6H132.294C130.695,197.867 127.698,202.133 123.302,205.4C118.972,208.6 113.843,210.2 107.915,210.2C100.189,210.2 93.927,207.633 89.132,202.5C84.402,197.367 82.038,190.867 82.038,183C82.038,175.333 84.436,168.9 89.231,163.7C94.027,158.5 100.222,155.9 107.815,155.9C115.409,155.9 121.537,158.467 126.199,163.6C130.928,168.667 133.293,175.067 133.293,182.8L133.193,186.5ZM107.815,165.6C104.085,165.6 100.988,166.7 98.523,168.9C96.059,171.1 94.527,174.033 93.927,177.7H121.503C120.971,174.033 119.505,171.1 117.107,168.9C114.709,166.7 111.612,165.6 107.815,165.6Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M186.835,179.2V209H174.946V177.9C174.946,170.033 171.682,166.1 165.154,166.1C161.624,166.1 158.793,167.233 156.662,169.5C154.597,171.767 153.564,174.867 153.564,178.8V209H141.675V157.1H152.665V164C153.931,161.667 155.862,159.733 158.46,158.2C161.058,156.667 164.288,155.9 168.152,155.9C175.345,155.9 180.541,158.633 183.738,164.1C188.134,158.633 193.996,155.9 201.323,155.9C207.384,155.9 212.047,157.8 215.311,161.6C218.574,165.333 220.206,170.267 220.206,176.4V209H208.317V177.9C208.317,170.033 205.053,166.1 198.525,166.1C194.928,166.1 192.064,167.267 189.933,169.6C187.868,171.867 186.835,175.067 186.835,179.2Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M279.335,186.5H239.87C240.336,190.633 241.835,193.933 244.366,196.4C246.897,198.8 250.227,200 254.357,200C257.088,200 259.552,199.333 261.751,198C263.949,196.667 265.514,194.867 266.447,192.6H278.436C276.837,197.867 273.84,202.133 269.444,205.4C265.114,208.6 259.985,210.2 254.057,210.2C246.331,210.2 240.069,207.633 235.274,202.5C230.544,197.367 228.18,190.867 228.18,183C228.18,175.333 230.578,168.9 235.374,163.7C240.169,158.5 246.364,155.9 253.957,155.9C261.551,155.9 267.679,158.467 272.341,163.6C277.071,168.667 279.435,175.067 279.435,182.8L279.335,186.5ZM253.957,165.6C250.227,165.6 247.13,166.7 244.665,168.9C242.201,171.1 240.669,174.033 240.069,177.7H267.645C267.113,174.033 265.647,171.1 263.249,168.9C260.851,166.7 257.754,165.6 253.957,165.6Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M298.807,157.1V164C300.006,161.733 301.971,159.833 304.702,158.3C307.5,156.7 310.863,155.9 314.793,155.9C320.921,155.9 325.651,157.767 328.981,161.5C332.378,165.233 334.077,170.2 334.077,176.4V209H322.187V177.9C322.187,174.233 321.321,171.367 319.589,169.3C317.924,167.167 315.36,166.1 311.896,166.1C308.099,166.1 305.102,167.233 302.904,169.5C300.772,171.767 299.707,174.9 299.707,178.9V209H287.817V157.1H298.807Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M368.8,198.4V208.7C367.335,209.1 365.27,209.3 362.606,209.3C352.481,209.3 347.419,204.2 347.419,194V166.6H339.526V157.1H347.419V143.6H359.308V157.1H369V166.6H359.308V192.8C359.308,196.867 361.24,198.9 365.103,198.9L368.8,198.4Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M171,6C171,2.686 173.686,0 177,0C199.091,0 217,17.909 217,40C217,43.314 214.314,46 211,46C207.686,46 205,43.314 205,40C205,24.536 192.464,12 177,12C173.686,12 171,9.314 171,6Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M199,94C199,97.314 196.314,100 193,100C170.909,100 153,82.091 153,60C153,56.686 155.686,54 159,54C162.314,54 165,56.686 165,60C165,75.464 177.536,88 193,88C196.314,88 199,90.686 199,94Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M141,64C137.686,64 135,61.314 135,58C135,35.909 152.909,18 175,18C178.314,18 181,20.686 181,24C181,27.314 178.314,30 175,30C159.536,30 147,42.536 147,58C147,61.314 144.314,64 141,64Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
<path
android:pathData="M229,36C232.314,36 235,38.686 235,42C235,64.091 217.091,82 195,82C191.686,82 189,79.314 189,76C189,72.686 191.686,70 195,70C210.464,70 223,57.464 223,42C223,38.686 225.686,36 229,36Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</vector>

View File

@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/signedOut"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
@ -42,5 +43,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/login_fragment"
@ -7,14 +7,13 @@
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/loginServerIcon"
@ -110,7 +109,7 @@
android:visibility="gone"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="22dp">
@ -120,25 +119,24 @@
style="@style/Style.Vector.Login.Button.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/auth_forgot_password"
app:layout_constraintStart_toStartOf="parent" />
android:layout_gravity="start"
android:text="@string/auth_forgot_password" />
<com.google.android.material.button.MaterialButton
android:id="@+id/loginSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:text="@string/auth_login"
app:layout_constraintEnd_toEndOf="parent"
tools:enabled="false"
tools:ignore="RelativeOverlap" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,22 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<!-- No scroll view in the screen, but use the style -->
<LinearLayout
style="@style/LoginFormScrollView"
android:layout_height="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
tools:ignore="MissingConstraints">
android:paddingBottom="16dp">
<ImageView
style="@style/LoginLogo"
android:layout_marginBottom="8dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/loginCaptchaNotice"
@ -36,16 +38,12 @@
</LinearLayout>
<!-- Id is defined in the style -->
<ProgressBar
android:id="@+id/loginCaptchaProgress"
android:layout_width="60dp"
android:layout_height="60dp"
app:layout_constraintBottom_toBottomOf="@id/loginFormScrollView"
app:layout_constraintEnd_toEndOf="@id/loginFormScrollView"
app:layout_constraintStart_toStartOf="@id/loginFormScrollView"
app:layout_constraintTop_toTopOf="@id/loginFormScrollView"
android:layout_gravity="center"
tools:ignore="UnknownId,NotSibling" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/login_fragment"
@ -7,14 +7,13 @@
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/loginGenericTextInputFormTitle"
@ -94,5 +93,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,19 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/resetPasswordTitle"
@ -103,5 +102,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
@ -48,5 +47,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,18 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:layout_width="wrap_content"
@ -48,5 +47,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,30 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<androidx.constraintlayout.widget.ConstraintLayout
style="@style/LoginFormContainer"
android:paddingBottom="@dimen/layout_vertical_margin">
<androidx.constraintlayout.widget.ConstraintLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/loginServerTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:text="@string/login_server_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title"
android:transitionName="loginTitleTransition"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/loginLogo" />
<TextView
android:id="@+id/loginServerText"
@ -193,5 +196,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/login_fragment"
@ -7,14 +7,13 @@
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<!-- Displayed only for EMS -->
<ImageView
@ -92,5 +91,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,27 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<androidx.constraintlayout.widget.ConstraintLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/loginSignupSigninServerIcon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:importantForAccessibility="no"
android:tint="?riotx_text_primary"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginLogo"
tools:src="@drawable/ic_logo_matrix_org"
tools:visibility="visible" />
@ -79,5 +84,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -18,18 +18,29 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/logoType"
android:layout_width="wrap_content"
android:layout_height="102dp"
android:src="@drawable/element_logotype_combined"
android:tint="?colorAccent"
android:id="@+id/loginSplashLogo"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/element_logo_green"
android:transitionName="loginLogoTransition"
app:layout_constraintBottom_toTopOf="@+id/logoType"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<ImageView
android:id="@+id/logoType"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginTop="8dp"
android:src="@drawable/element_logotype"
android:tint="?colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginSplashLogo" />
<TextView
android:id="@+id/loginSplashTitle"
android:layout_width="0dp"

View File

@ -1,26 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.constraintlayout.widget.ConstraintLayout
style="@style/LoginFormScrollView"
android:layout_height="0dp"
android:layout_height="match_parent"
tools:ignore="MissingConstraints">
<ImageView
style="@style/LoginLogo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/loginTermsTitle"
style="@style/TextAppearance.Vector.Login.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:paddingStart="36dp"
android:paddingEnd="36dp"
android:text="@string/login_terms_title"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/loginLogo" />
<TextView
android:id="@+id/loginTermsNotice"
@ -53,5 +59,5 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -1,19 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/login_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<LinearLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/loginWaitForEmailTitle"
@ -43,5 +42,5 @@
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/loginLogo"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="32dp"
android:importantForAccessibility="no"
android:src="@drawable/element_logo_green" />

View File

@ -47,7 +47,7 @@
<TextView
android:id="@+id/riot_desktop_web"
android:id="@+id/app_desktop_web"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@ -59,7 +59,7 @@
app:layout_constraintTop_toBottomOf="@id/monitorIcon" />
<TextView
android:id="@+id/riot_ios_android"
android:id="@+id/app_ios_android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@ -75,7 +75,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="riot_ios_android,riot_desktop_web" />
app:constraint_referenced_ids="app_ios_android,app_desktop_web" />
<TextView

View File

@ -727,7 +727,7 @@ Vaši e-mailovou adresu můžete přidat k profilu v nastavení.</string>
<string name="settings_messages_sent_by_bot">Zprávy poslané botem</string>
<string name="settings_background_sync">Synchronizace na pozadí</string>
<string name="settings_background_fdroid_sync_mode">Režim synchronizace na pozadí (experimentální)</string>
<string name="settings_background_fdroid_sync_mode">Režim synchronizace na pozadí</string>
<string name="settings_background_fdroid_sync_mode_battery">Optimalizován pro baterii</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element bude synchronizovat na pozadí způsobem, který šetří omezené zdroje zařízení (baterii).
\nV závislosti na stavu zdrojů zařízení může být sync operačním systémem odložen.</string>
@ -2609,4 +2609,18 @@ Vaši e-mailovou adresu můžete přidat k profilu v nastavení.</string>
<string name="auth_pin_confirm_to_disable_title">Pro vypnutí PINu potvrďte PIN</string>
<string name="error_opening_banned_room">Nemohu otevřít místnost, z níž jste byli vykázáni.</string>
<string name="room_error_not_found">Nemohu najít tuto místnost. Ujistěte se, že existuje.</string>
<plurals name="seconds">
<item quantity="one">%d vteřina</item>
<item quantity="few">%d vteřiny</item>
<item quantity="other">%d vteřin</item>
</plurals>
<string name="settings_show_room_member_state_events">Zobrazit stavové události účastníků v místnosti</string>
<string name="settings_show_room_member_state_events_summary">Zahrnuje události pozvat/vstoupit/opustit/vykopnout/vykázat a změny avatara/veřejného jména.</string>
<string name="sent_a_poll">Průzkum</string>
<string name="sent_a_bot_buttons">Tlačítka botů</string>
<string name="sent_a_reaction">Reagovali skrze: %s</string>
<string name="sent_verification_conclusion">Výsledek ověření</string>
<string name="universal_link_malformed">Odkaz byl chybně zformován</string>
</resources>

View File

@ -258,7 +258,7 @@
Bitte erlaube den Zugriff im nächsten Dialog, um den Anruf durchzuführen.</string>
<string name="permissions_rationale_msg_contacts">Element kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer anhand ihrer Email-Adresse und Telefonnummer zu finden. Wenn du der Nutzung deines Adressbuchs zu diesem Zweck zustimmst, erlaube den Zugriff im nächsten Popup-Fenster.</string>
<string name="permissions_msg_contacts_warning_other_androids">Element kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer anhand ihrer Email-Adresse und Telefonnummer zu finden.
<string name="permissions_msg_contacts_warning_other_androids">Element kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer anhand ihrer E-Mail-Adresse und Telefonnummer zu finden.
\nStimmst du der Nutzung deines Adressbuchs zu diesem Zweck zu\?</string>
<string name="permissions_action_not_performed_missing_permissions">Entschuldige. Die Aktion wurde aufgrund fehlender Berechtigungen nicht ausgeführt</string>
@ -1055,7 +1055,7 @@ Achtung: Diese Datei wird vielleicht gelöscht, wenn die App deinstalliert wird.
<string name="x_plus">%d+</string>
<string name="call_anyway">Trotzdem anrufen</string>
<string name="room_participants_action_kick">Kicken</string>
<string name="room_participants_action_kick">Entfernen</string>
<string name="reason_hint">Grund</string>
<string name="settings_inline_url_preview_summary">Linkvorschau im Chat aktivieren, falls dein Home-Server diese Funktion unterstützt.</string>
@ -1342,7 +1342,7 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="keys_backup_banner_in_progress">Sichere Schlüssel…</string>
<string name="keys_backup_info_keys_all_backup_up">Alle Schlüssel gesichert</string>
<string name="keys_backup_info_keys_all_backup_up">Alle Schlüssel sind gesichert</string>
<plurals name="keys_backup_info_keys_backing_up">
<item quantity="one">Sichere %d Schlüssel…</item>
<item quantity="other">Sichere %d Schlüssel…</item>
@ -1669,9 +1669,9 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="settings_add_3pid_authentication_needed">Authentifizierung benötigt</string>
<string name="settings_background_fdroid_sync_mode">Hintergrundsynchronisierungsmodus (experimentell)</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element wird sich im Hintergrund auf eine Art synchronisieren die Ressourcen des Geräts schont (Akku).
\nAbhängig von dem Ressourcen-Statuses deines Geräts kann dein System die Synchronisierung verschieben.</string>
<string name="settings_background_fdroid_sync_mode">Hintergrund-Synchronisierungsmodus</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element wird sich im Hintergrund auf eine Art synchronisieren die Ressourcen des Geräts (Akku) schont.
\nAbhängig vom Ressourcen-Status deines Geräts kann dein System die Synchronisierung verschieben.</string>
<string name="settings_background_fdroid_sync_mode_real_time_description">Element wird sich im Hintergrund periodisch zu einem bestimmten Zeitpunkt synchronisieren (konfigurierbar).
\nDies wird Funk- und Akkunutzung beeinflussen. Es wird eine permanente Benachrichtigung geben, die sagt, dass Element auf Ereignisse lauscht.</string>
<string name="settings_set_workmanager_delay_summary">%s
@ -1808,7 +1808,7 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="login_splash_submit">Beginne</string>
<string name="login_server_title">Wähle einen Server</string>
<string name="login_server_text">Genau wie bei Emails haben Accounts ein Zuhause, auch wenn du mit jedem kommunizieren kannst</string>
<string name="login_server_text">Genau wie bei E-Mails haben Accounts ein Zuhause, auch wenn du mit jedem kommunizieren kannst</string>
<string name="login_server_matrix_org_text">Folge Millionen anderen kostenlos auf dem größten öffentlichen Server</string>
<string name="login_server_modular_text">Premium Hosting für Organisationen</string>
<string name="login_server_modular_learn_more">Mehr erfahren</string>
@ -2118,7 +2118,7 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="settings_active_sessions_list">Aktive Sitzungen</string>
<string name="settings_active_sessions_show_all">Zeige alle Sitzungen</string>
<string name="settings_active_sessions_manage">Verwalte Sitzungen</string>
<string name="settings_active_sessions_manage">Sitzungen verwalten</string>
<string name="settings_active_sessions_signout_device">Diese Sitzung abmelden</string>
<string name="settings_failed_to_get_crypto_device_info">Keine kryptografischen Informationen verfügbar</string>
@ -2271,7 +2271,7 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="error_failed_to_import_keys">Import der Schlüssel fehlgeschlagen</string>
<string name="settings_notification_configuration">Benachrichtigungskonfiguration</string>
<string name="settings_messages_at_room">\@raum enthaltende Nachrichten</string>
<string name="settings_messages_at_room">Nachrichten, die @raum enthalten</string>
<string name="settings_messages_in_e2e_group_chat">Verschlüsselte Nachrichten in Gruppenchats</string>
<string name="settings_notification_advanced_summary">Setze die Benachrichtigungspräferenz abhängig vom Ereignistyp</string>
@ -2353,7 +2353,7 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="use_latest_app">Nutze die neueste Version von Element auf deinen anderen Geräten:</string>
<string name="command_description_discard_session">Erzwingt das Verferfen der aktuell ausgehende Gruppensitzung in einem verschlüsseltem Raum</string>
<string name="command_description_discard_session_not_handled">Wird nur in verschlüsselten Räumen unterstützt</string>
<string name="enter_secret_storage_passphrase_or_key">Benutze dein %1$s oder deinen %2$s um fortzufahren.</string>
<string name="enter_secret_storage_passphrase_or_key">Benutze deine %1$s oder deinen %2$s um fortzufahren.</string>
<string name="use_recovery_key">Wiederherstellungsschlüssel verwenden</string>
<string name="enter_secret_storage_input_key">Wähle deinen Wiederherstellungsschüssel, gib ihn ein oder füge ihn aus der Zwischenablage ein</string>
<string name="keys_backup_recovery_key_error_decrypt">Sicherung konnte mit diesem Wiederherstellungsschlüssel nicht entschlüsselt werden. Bitte stelle sicher, dass du den korrekten Wiederherstellungsschlüssel eingegeben hast.</string>
@ -2478,9 +2478,9 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="event_redacted">Nachricht gelöscht</string>
<string name="settings_show_redacted">Zeige gelöschte Nachrichten</string>
<string name="settings_show_redacted_summary">Zeige einen Platzhalter für gelöschte Nachrichten</string>
<string name="labs_show_unread_notifications_as_tab">Füge dedizierten Tab für ungelesene Benachrichtungen zur Hauptansicht hinzu.</string>
<string name="labs_show_unread_notifications_as_tab">Dedizierten Tab für ungelesene Nachrichten zur Hauptansicht hinzufügen</string>
<string name="settings_discovery_confirm_mail_not_clicked">Wir haben dir eine Bestätigungsmail an %s gesendet. Bitte prüfe deine Emails und klicke auf den Bestätigungslink</string>
<string name="settings_discovery_confirm_mail_not_clicked">Wir haben dir eine Bestätigungsmail an %s gesendet. Bitte prüfe deine E-Mails und klicke auf den Bestätigungslink</string>
<string name="settings_text_message_sent_wrong_code">Der Verifizierungscode ist nicht korrekt.</string>
<string name="uploads_media_title">MEDIEN</string>
@ -2497,13 +2497,13 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="login_server_url_form_common_notice">Gib die Adresse des Servers ein, den du benutzen möchtest</string>
<string name="login_connect_using_matrix_id_notice">Wenn du deine Matrixkennung und dein Passwort weißt, kannst du alternativ diese Methode nutzen:</string>
<string name="login_connect_using_matrix_id_submit">Einloggen mit Matrix ID</string>
<string name="login_signin_matrix_id_title">Einloggen mit Matrix ID</string>
<string name="login_signin_matrix_id_notice">Wenn du einen Account auf einem Homeserver eingerichtet hast, benutze deine Matrix ID (z.B. @benutzer@domain.com) und Passwort.</string>
<string name="login_signin_matrix_id_hint">Matrix ID</string>
<string name="login_connect_using_matrix_id_submit">Einloggen mit Matrix-ID</string>
<string name="login_signin_matrix_id_title">Einloggen mit Matrix-ID</string>
<string name="login_signin_matrix_id_notice">Wenn du einen Account auf einem Home-Server eingerichtet hast, benutze deine Matrix-ID (z.B. @benutzer:domain.com) und Passwort.</string>
<string name="login_signin_matrix_id_hint">Matrix-ID</string>
<string name="login_signin_matrix_id_password_notice">Wenn du dein Passwort nicht weißt, gehe zurück um es zurücksetzen zu lassen.</string>
<string name="login_signin_matrix_id_error_invalid_matrix_id">Dies ist keine gültige Benutzerkennung. Erwartetes Format: \'@benutzer:homeserver.org\'</string>
<string name="autodiscover_well_known_error">Es konnte kein gültiger Homeserver gefunden werden. Bitte prüfe deine Kennung</string>
<string name="autodiscover_well_known_error">Es konnte kein gültiger Home-Server gefunden werden. Bitte prüfe deine Kennung</string>
<string name="send_a_sticker">Sticker</string>
@ -2621,7 +2621,7 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="settings_security_pin_code_title">Aktiviere PIN</string>
<string name="settings_security_pin_code_summary">Wenn du deine PIN zurücksetzen möchtest, tippe \"PIN vergessen\" um dich abzumelden und sie anschließend zurückzusetzen.</string>
<string name="auth_pin_confirm_to_disable_title">Bestätige PIN um die PIN zu deaktivieren</string>
<string name="labs_merge_e2e_in_timeline">Fasse \"Nicht entschlüsselbar\"-Fehler im Chatverlauf zu Hinweisen zusammen</string>
<string name="labs_merge_e2e_in_timeline">\"Nicht entschlüsselbar\"-Fehler im Chatverlauf zusammenfassen</string>
<string name="settings_call_show_confirmation_dialog_title">Verhindere versehentliche Anrufe</string>
<string name="settings_call_show_confirmation_dialog_summary">Bitte um Bestätigung, bevor du einen Anruf tätigst</string>
<string name="bottom_sheet_setup_secure_backup_submit">Konfiguration</string>
@ -2673,4 +2673,15 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine
<string name="error_opening_banned_room">Raum, indem du gebannt wurdest, kann nicht geöffnet werden.</string>
<string name="room_error_not_found">Raum kann nicht gefunden werden. Stelle sicher, dass er existiert.</string>
<plurals name="seconds">
<item quantity="one">%d Sekunde</item>
<item quantity="other">%d Sekunden</item>
</plurals>
<string name="settings_show_room_member_state_events">Zeige Status-Ereignisse der Raum-Mitglieder</string>
<string name="settings_show_room_member_state_events_summary">Bezieht Einladungs-/Beitritts-/Verlassen-/Entfernen-/Verbannen-Ereignisse und Avatar-/Anzeigenamen-Wechsel mit ein.</string>
<string name="sent_a_poll">Umfrage</string>
<string name="sent_a_bot_buttons">Bot-Schaltflächen</string>
<string name="sent_a_reaction">Reagierte mit: %s</string>
<string name="universal_link_malformed">Der Link war nicht korrekt</string>
</resources>

View File

@ -1048,7 +1048,7 @@
\nKas sa oled ikka kindel\?</string>
<string name="room_participants_power_level_demote_warning_title">Kas vähendad enda õigusi\?</string>
<string name="room_participants_power_level_demote_warning_prompt">Kuna sa vähendad enda õigusi, siis sul ei pruugi enam olla võimalik seda muutust tagasi pöörata. Kui sa juhtumisi oled viimane haldusõigustega kasutaja jututoas, siis hiljem on võimatu samu õigusi tagasi saada.</string>
<string name="room_participants_power_level_demote_warning_prompt">Kuna sa vähendad enda õigusi, siis sul ei pruugi hiljem olla võimalik seda muutust tagasi pöörata. Kui sa juhtumisi oled viimane haldusõigustega kasutaja jututoas, siis hiljem on võimatu samu õigusi tagasi saada.</string>
<string name="room_participants_power_level_demote">Vähenda enda õigusi</string>
@ -1277,7 +1277,7 @@
<string name="settings_messages_sent_by_bot">Robotite saadetud sõnumid</string>
<string name="settings_background_sync">Andmete sünkroniseerimine taustal</string>
<string name="settings_background_fdroid_sync_mode">Andmete taustal sünkroniseerimise režiim (katseline)</string>
<string name="settings_background_fdroid_sync_mode">Andmete taustal sünkroniseerimise režiim</string>
<string name="settings_background_fdroid_sync_mode_battery">Optimeeritud akukestust silmas pidades</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element sünkroniseerib taustal nii, et see arvestab seadme piiratud ressursse (aku).
\nSõltuvalt seadme olekust võib operatsioonisüsteem sünkroniseerimist edasi lükata.</string>
@ -2331,8 +2331,8 @@
<string name="sent_an_audio_file">Helifail</string>
<string name="sent_a_file">Fail</string>
<string name="room_profile_not_encrypted_subtitle">Sõnumid siin jututoas on läbivalt krüptitud.</string>
<string name="room_profile_encrypted_subtitle">Sõnumid siis jututoas kasutavad läbivat krüptimist.
\n
<string name="room_profile_encrypted_subtitle">Sõnumid siin jututoas kasutavad läbivat krüptimist.
\n
\nSinu sõnumid on turvatud ning ainult sinul ja saaja(te)l on unikaalsed võtmed selliste sõnumite lugemiseks.</string>
<string name="room_profile_section_security">Turvalisus</string>
<string name="room_profile_section_security_learn_more">Lisateave</string>
@ -2544,4 +2544,17 @@
<string name="error_opening_banned_room">Ei ole võimalik avada sellise jututoa vaadet, kus sulle on seatud suhtluskeeld.</string>
<string name="room_error_not_found">Ei leia sellist jututuba. Palun kontrolli, et ta ikka olemas on.</string>
<plurals name="seconds">
<item quantity="one">%d sekund</item>
<item quantity="other">%d sekundit</item>
</plurals>
<string name="settings_show_room_member_state_events">Näita jututoa liikmete olekusündmusi</string>
<string name="settings_show_room_member_state_events_summary">Sealhulgas kutsumisi, liitumisi, lahkumisi, müksamisi, keelamisi ning tunnuspildi ja kuvatava nime muutusi.</string>
<string name="sent_a_poll">Küsitlus</string>
<string name="sent_a_bot_buttons">Robotinupud</string>
<string name="sent_a_reaction">Reageeris: %s</string>
<string name="sent_verification_conclusion">Verifitseerimise tulemus</string>
<string name="universal_link_malformed">Link oli vigane</string>
</resources>

View File

@ -2539,4 +2539,14 @@
<string name="settings_developer_mode_summary">Askar n uneflay yermed timahilin i yeffren yerna yezmer daɣen ad yerr asnas ur yerkid ara akken iwata. I yineflayen kan!</string>
<string name="settings_developer_mode_fail_fast_title">Abrir arurad</string>
<plurals name="seconds">
<item quantity="one">%d n tsint</item>
<item quantity="other">%d n tsintin</item>
</plurals>
<string name="settings_show_room_member_state_events">Sken tidyanin n waddad n uɛeggal n texxamt</string>
<string name="sent_a_bot_buttons">Tiqeffalin n uṛubut</string>
<string name="sent_verification_conclusion">Taggrayt n usenqed</string>
<string name="universal_link_malformed">Aseɣwen ur yemsil ara akken iwata</string>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

View File

@ -164,8 +164,8 @@
<string name="auth_forgot_password">Esqueceu sua senha?</string>
<string name="auth_use_server_options">Use opções para servidor personalizado (avançado)</string>
<string name="auth_email_validation_message">Por favor, verifique o seu e-mail para continuar a inscrição</string>
<string name="auth_threepid_warning_message">Atualmente, registrar-se com e-mail e número de telefone ao mesmo tempo não é possível. Apenas o número de telefone será levado em consideração.
\n
<string name="auth_threepid_warning_message">Atualmente, registrar-se com e-mail e número de telefone ao mesmo tempo não é possível. Apenas o número de telefone será levado em consideração.
\n
\nNo entanto, você pode adicionar o endereço de e-mail ao seu perfil nas configurações.</string>
<string name="auth_recaptcha_message">Este servidor local quer se certificar de que você não é um robô</string>
<string name="auth_username_in_use">Nome de usuário indisponível</string>
@ -177,8 +177,8 @@
<string name="auth_reset_password_missing_password">Uma nova senha precisa ser inserida.</string>
<string name="auth_reset_password_email_validation_message">Um e-mail foi enviado para %s. Após clicar no link contido no e-mail, clique abaixo.</string>
<string name="auth_reset_password_error_unauthorized">Falha ao confirmar o endereço de e-mail: certifique-se de clicar no link do e-mail</string>
<string name="auth_reset_password_success_message">Sua senha foi alterada.
\n
<string name="auth_reset_password_success_message">Sua senha foi alterada.
\n
\nVocê foi desconectado de todas as sessões e não receberá mais notificações. Para reativar as notificações, faça login novamente em cada aparelho.</string>
<!-- Login Screen -->
@ -247,8 +247,8 @@
<!-- permissions Android M -->
<string name="permissions_rationale_popup_title">Informação</string>
<string name="permissions_rationale_msg_storage">Element precisa de permissão para acessar sua galeria de fotos e vídeos para enviar e salvar anexos.
\n
<string name="permissions_rationale_msg_storage">Element precisa de permissão para acessar sua galeria de fotos e vídeos para enviar e salvar anexos.
\n
\nPor favor, permita o acesso na próxima tela para poder enviar arquivos do seu celular.</string>
<string name="permissions_rationale_msg_camera">Element necessita permissão para acessar sua câmera para poder tirar fotos e fazer chamadas de vídeo.</string>
<string name="permissions_rationale_msg_camera_explanation">"
@ -258,12 +258,12 @@
<string name="permissions_rationale_msg_record_audio_explanation">"
\n
\nPor favor, permita o acesso na próxima tela para fazer a chamada."</string>
<string name="permissions_rationale_msg_camera_and_audio">Element necessita permissão para acessar sua câmera e seu microfone para fazer chamadas de vídeo.
\n
<string name="permissions_rationale_msg_camera_and_audio">Element necessita permissão para acessar sua câmera e seu microfone para fazer chamadas de vídeo.
\n
\nPor favor, permita o acesso na próxima tela para fazer a chamada.</string>
<string name="permissions_rationale_msg_contacts">Element precisa de permissão para acessar os seus contatos para poder encontrar outros usuários a partir de seus e-mails e números de telefone. Se você concordar em usar a sua lista de contatos para esse propósito, permita o acesso na próxima janela pop-up.</string>
<string name="permissions_msg_contacts_warning_other_androids">Element precisa de permissão para acessar os seus contatos para poder encontrar outros usuários a partir de seus e-mails e números de telefone.
\n
<string name="permissions_msg_contacts_warning_other_androids">Element precisa de permissão para acessar os seus contatos para poder encontrar outros usuários a partir de seus e-mails e números de telefone.
\n
\nVocê concorda em usar a sua lista de contatos para esse propósito\?</string>
<string name="permissions_action_not_performed_missing_permissions">Desculpe. A ação não foi realizada, por falta de permissão</string>
@ -286,7 +286,7 @@
<!-- Room Preview -->
<string name="room_preview_invitation_format">Você foi convidada(o) a entrar nesta sala por %s</string>
<string name="room_preview_unlinked_email_warning">Este convite foi enviado a %s, que não está associado com esta conta.
<string name="room_preview_unlinked_email_warning">Este convite foi enviado a %s, que não está associado com esta conta.
\nVocê pode querer fazer login com uma conta diferente, ou adicionar este e-mail à sua conta.</string>
<string name="room_preview_try_join_an_unknown_room">Você está tentando acessar %s. Quer entrar na sala para poder participar da conversa?</string>
<string name="room_preview_try_join_an_unknown_room_default">uma sala</string>
@ -507,8 +507,8 @@
<string name="settings_confirm_password">Confirme a nova senha</string>
<string name="settings_fail_to_update_password">Não consegui atualizar a senha</string>
<string name="settings_password_updated">Sua senha foi atualizada</string>
<string name="settings_unignore_user">Mostrar todas as mensagens de %s\?
\n
<string name="settings_unignore_user">Mostrar todas as mensagens de %s\?
\n
\nNote que esta ação irá reiniciar o aplicativo e pode levar algum tempo.</string>
<string name="settings_delete_notification_targets_confirmation">Deseja deixar de notificar este aparelho\?</string>
@ -592,7 +592,7 @@
<string name="room_settings_addresses_e2e_enabled">A criptografia está ativada nesta sala.</string>
<string name="room_settings_addresses_e2e_disabled">A criptografia está desativada nesta sala.</string>
<string name="room_settings_addresses_e2e_encryption_warning">Ativar criptografia·
<string name="room_settings_addresses_e2e_encryption_warning">Ativar criptografia·
\n(atenção: não é possível desativar depois!)</string>
<!-- Directory -->
@ -900,8 +900,8 @@ Atenção: este arquivo poderá ser apagado se o aplicativo for desinstalado.</s
<string name="option_send_voice">Enviar áudio</string>
<string name="option_send_sticker">Enviar uma figurinha</string>
<string name="no_sticker_application_dialog_content">No momento, você não tem nenhum pacote de figurinhas ativado.
\n
<string name="no_sticker_application_dialog_content">No momento, você não tem nenhum pacote de figurinhas ativado.
\n
\nQuer adicionar alguns agora\?</string>
<string name="go_on_with">continuar com…</string>
@ -1002,10 +1002,10 @@ Atenção: este arquivo poderá ser apagado se o aplicativo for desinstalado.</s
<string name="dialog_user_consent_submit">Revisar agora</string>
<string name="deactivate_account_title">Desativar minha conta</string>
<string name="deactivate_account_content">Isso tornará sua conta permanentemente inutilizável. Você não conseguirá efetuar login e ninguém poderá registrar novamente o mesmo ID de usuário. Isso fará com que sua conta saia de todas as salas das quais está participando e removerá os detalhes de sua conta do servidor de identidade. <b>Esta ação é irreversível</b>.
\n
\nDesativar sua conta <b>não faz com que, por padrão, suas mensagens enviadas sejam apagadas</b>. Se você deseja que suas mensagens também sejam apagadas, marque a opção abaixo.
\n
<string name="deactivate_account_content">Isso tornará sua conta permanentemente inutilizável. Você não conseguirá efetuar login e ninguém poderá registrar novamente o mesmo ID de usuário. Isso fará com que sua conta saia de todas as salas das quais está participando e removerá os detalhes de sua conta do servidor de identidade. <b>Esta ação é irreversível</b>.
\n
\nDesativar sua conta <b>não faz com que, por padrão, suas mensagens enviadas sejam apagadas</b>. Se você deseja que suas mensagens também sejam apagadas, marque a opção abaixo.
\n
\nA visibilidade de mensagens na Matrix é semelhante a um e-mail. O fato de apagarmos suas mensagens significa que suas mensagens enviadas não serão compartilhadas com nenhum usuário novo ou ainda não registrado, mas os usuários registrados que já tiveram acesso a essas mensagens ainda terão acesso uma cópia delas.</string>
<string name="deactivate_account_delete_checkbox">Quando minha conta for desativada, apague todas as mensagens que eu enviei (Atenção: isso fará com que futuros usuários tenham uma visão incompleta das conversas)</string>
<string name="deactivate_account_prompt_password">Para continuar, entre com sua senha:</string>
@ -1063,14 +1063,14 @@ Atenção: este arquivo poderá ser apagado se o aplicativo for desinstalado.</s
<string name="settings_troubleshoot_test_account_settings_title">Configurações da conta</string>
<string name="settings_troubleshoot_test_account_settings_success">Notificações estão ativadas para sua conta.</string>
<string name="settings_troubleshoot_test_account_settings_failed">Notificações estão desativadas para sua conta.
<string name="settings_troubleshoot_test_account_settings_failed">Notificações estão desativadas para sua conta.
\nPor favor, revise as configurações da conta.</string>
<string name="settings_troubleshoot_test_account_settings_quickfix">Ativar</string>
<string name="settings_troubleshoot_test_device_settings_title">Configurações da sessão</string>
<string name="settings_troubleshoot_test_device_settings_success">Notificações estão ativadas nesta sessão.</string>
<string name="room_participants_action_kick">Remover da sala</string>
<string name="settings_troubleshoot_test_device_settings_failed">Notificações não estão ativadas nesta sessão.
<string name="settings_troubleshoot_test_device_settings_failed">Notificações não estão ativadas nesta sessão.
\nPor favor, revise as configurações do Element.</string>
<string name="settings_troubleshoot_test_device_settings_quickfix">Ativar</string>
@ -1126,10 +1126,10 @@ Tente reiniciar a aplicação.</string>
<string name="startup_notification_fdroid_battery_optim_title">Conexão em segundo plano</string>
<string name="startup_notification_fdroid_battery_optim_message">Element precisa manter um baixo impacto na conexão em segundo plano para ter notificações confiáveis.
Na próxima tela, você será solicitado a permitir que o Element funcione sempre em segundo plano, por favor aceite.</string>
<string name="settings_troubleshoot_test_bg_restricted_success">Restrições de segundo plano estão desativadas para o Element. Este teste deve ser realizado usando dados móveis (sem Wi-Fi).
<string name="settings_troubleshoot_test_bg_restricted_success">Restrições de segundo plano estão desativadas para o Element. Este teste deve ser realizado usando dados móveis (sem Wi-Fi).
\n%1$s</string>
<string name="settings_troubleshoot_test_bg_restricted_failed">Restrições em segundo plano estão ativadas para o Element.
\nO aplicativo funciona bastante restringido enquanto está em segundo plano, o que pode afetar as notificações.
<string name="settings_troubleshoot_test_bg_restricted_failed">Restrições em segundo plano estão ativadas para o Element.
\nO aplicativo funciona bastante restringido enquanto está em segundo plano, o que pode afetar as notificações.
\n%1$s</string>
<string name="settings_send_markdown">Formatação de texto</string>
<string name="settings_send_markdown_summary">Formatar o texto das mensagens a serem enviadas. Por exemplo: inserir asteriscos antes e depois do texto, mostrará o texto em itálico.</string>
@ -1205,14 +1205,14 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="settings_troubleshoot_test_bing_settings_failed_to_load_rules">Falha ao carregar regras personalizadas, tente novamente.</string>
<string name="settings_troubleshoot_test_bing_settings_quickfix">Verifique as configurações</string>
<string name="settings_troubleshoot_test_fcm_failed_too_many_registration">[%1$s]
<string name="settings_troubleshoot_test_fcm_failed_too_many_registration">[%1$s]
\nEste erro está fora do controle do Element e, de acordo com o Google, esse erro indica que o aparelho tem muitos aplicativos registrados com FCM. O erro só ocorre nos casos em que há números extremos de aplicativos, portanto, isso não deve afetar o usuário comum.</string>
<string name="ignore">Bloquear</string>
<string name="auth_login_sso">Entre com o login único</string>
<string name="login_error_unknown_host">Este endereço não está acessível. Por favor, verifique-o</string>
<string name="login_error_ssl_handshake">Seu aparelho está usando um protocolo de segurança TLS desatualizado, vulnerável a ataques. Para sua segurança, você não poderá se conectar</string>
<string name="settings_troubleshoot_test_fcm_failed_service_not_available">[%1$s]
<string name="settings_troubleshoot_test_fcm_failed_service_not_available">[%1$s]
\nEste erro está fora de controle da Element. Isso pode ocorrer por vários motivos. Talvez funcione se você tentar novamente mais tarde. Você também pode verificar se o uso de dados do Google Play Service está restrito nas configurações do sistema, ou se o relógio do seu aparelho está correto. O erro também pode ocorrer em ROMs personalizadas.</string>
<string name="notification_sync_init">Iniciando o serviço</string>
<string name="settings_troubleshoot_test_fcm_failed_account_missing">[%1$s]
@ -1257,7 +1257,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="call_failed_dont_ask_again">Não pergunte novamente</string>
<string name="call_failed_no_connection">A chamada falhou</string>
<string name="call_failed_no_connection_description">Falha ao estabelecer conexão em tempo real.
<string name="call_failed_no_connection_description">Falha ao estabelecer conexão em tempo real.
\nPor favor, peça ao administrador do seu servidor para configurar um servidor TURN, de modo que as chamadas funcionem de maneira estável.</string>
<string name="call_select_sound_device">Selecione a caixa de som</string>
@ -1317,10 +1317,10 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="settings_notification_privacy_no_background_sync">O aplicativo <b>não</b> precisa de se conectar ao servidor em segundo plano, isto deve reduzir a utilização da bateria</string>
<string name="settings_background_fdroid_sync_mode">Sincronização em segundo plano (Experimental)</string>
<string name="settings_background_fdroid_sync_mode_battery">Optimizado para bateria</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element sincronizará em segundo plano para preservar os recursos limitados do aparelho (bateria).
<string name="settings_background_fdroid_sync_mode_battery_description">Element sincronizará em segundo plano para preservar os recursos limitados do aparelho (bateria).
\nDependendo do estado dos recursos do seu aparelho, a sincronização pode ser adiada pelo sistema operacional.</string>
<string name="settings_background_fdroid_sync_mode_real_time">Optimizado em tempo real</string>
<string name="settings_background_fdroid_sync_mode_real_time_description">O Element sincronizará periodicamente em segundo plano, no momento estabelecido (configurável).
<string name="settings_background_fdroid_sync_mode_real_time_description">O Element sincronizará periodicamente em segundo plano, no momento estabelecido (configurável).
\nIsso afetará o uso de dados e da bateria. Haverá uma notificação permanente informando que o Element está sincronizando.</string>
<string name="settings_background_fdroid_sync_mode_disabled">Sem sincronização em segundo plano</string>
<string name="settings_background_fdroid_sync_mode_disabled_description">Você não será notificado sobre mensagens recebidas quando o Element estiver em segundo plano.</string>
@ -1328,7 +1328,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="settings_set_workmanager_delay">Intervalo de sincronização preferido</string>
<string name="settings_set_workmanager_delay_summary">%s
<string name="settings_set_workmanager_delay_summary">%s
\nA sincronização pode ser adiada dependendo dos recursos (bateria) ou do estado do aparelho (modo de suspensão).</string>
<string name="settings_integrations">Integrações</string>
<string name="settings_integrations_summary">Use o Gerenciador de Integrações para gerenciar bots, pontes, widgets e pacotes de figurinhas.
@ -1405,7 +1405,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="room_widget_permission_added_by">Widget adicionado por:</string>
<string name="room_widget_permission_webview_shared_info_title">A sua utilização pode definir cookies e compartilhar dados com %s:</string>
<string name="room_widget_permission_shared_info_title">A sua utilização pode compartilhar dados com %s:</string>
<string name="room_widget_failed_to_load">Falha ao carregar widget.
<string name="room_widget_failed_to_load">Falha ao carregar widget.
\n%s</string>
<string name="room_widget_reload">Recarregar widget</string>
<string name="room_widget_open_in_browser">Abrir no navegador</string>
@ -1430,12 +1430,12 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="integration_manager_not_configured">O gerenciador de integrações não está configurado.</string>
<string name="widget_integration_review_terms">Para continuar, você precisa aceitar os termos de serviço.</string>
<string name="you_added_a_new_device_with_info">Uma nova sessão está solicitando chaves de criptografia. Nome da sessão: %1$s
\nVisto por último às: %2$s
<string name="you_added_a_new_device_with_info">Uma nova sessão está solicitando chaves de criptografia. Nome da sessão: %1$s
\nVisto por último às: %2$s
\nSe você não fez login em outra sessão, ignore essa solicitação.</string>
<string name="your_unverified_device_requesting_with_info">Uma nova sessão está solicitando chaves de criptografia.
\nNome da sessão: %1$s
\nVisto por último às: %2$s
<string name="your_unverified_device_requesting_with_info">Uma nova sessão está solicitando chaves de criptografia.
\nNome da sessão: %1$s
\nVisto por último às: %2$s
\nSe você não fez login em outra sessão, ignore essa solicitação.</string>
<string name="start_verification_short_label">Confirmar</string>
@ -1452,8 +1452,8 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="keys_backup_no_session_error">Nenhuma sessão Matrix disponível</string>
<string name="keys_backup_setup_step1_title">Nunca perca mensagens criptografadas</string>
<string name="keys_backup_setup_step1_description">As mensagens em salas criptografadas são protegidas com a criptografia de ponta a ponta. Somente você e o(s) destinatário(s) têm as chaves para ler essas mensagens.
\n
<string name="keys_backup_setup_step1_description">As mensagens em salas criptografadas são protegidas com a criptografia de ponta a ponta. Somente você e o(s) destinatário(s) têm as chaves para ler essas mensagens.
\n
\nFaça backup de suas chaves de segurança para evitar perdê-las.</string>
<string name="keys_backup_setup">Comece a usar o Backup de Chave</string>
<string name="keys_backup_setup_step1_advanced">(Avançado)</string>
@ -1591,7 +1591,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="keys_backup_info_title_algorithm">Algoritmo</string>
<string name="autodiscover_invalid_response">Resposta de descoberta inválida no servidor local</string>
<string name="autodiscover_well_known_autofill_dialog_title">Completar Automaticamente as Opções do Servidor</string>
<string name="autodiscover_well_known_autofill_dialog_message">Element detectou uma configuração personalizada do servidor para a sua ID de usuário \"%1$s\":
<string name="autodiscover_well_known_autofill_dialog_message">Element detectou uma configuração personalizada do servidor para a sua ID de usuário \"%1$s\":
\n%2$s</string>
<string name="autodiscover_well_known_autofill_confirm">Usar a Configuração</string>
@ -1632,8 +1632,8 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="sas_error_m_user">O contato cancelou a confirmação</string>
<string name="sas_error_m_timeout">O tempo de confirmação expirou</string>
<string name="sas_error_m_unknown_transaction">A sessão não sabe sobre essa transacção</string>
<string name="sas_error_m_unknown_method">A sessão não pode chegar a acordo sobre a chave, método hash, MAC, or SAS</string>
<string name="sas_error_m_unknown_transaction">A sessão não sabe sobre essa transação</string>
<string name="sas_error_m_unknown_method">A sessão não concorda com a chave, método hash, MAC, ou SAS</string>
<string name="sas_error_m_mismatched_commitment">O compromisso de hash não coincidiu</string>
<string name="sas_error_m_mismatched_sas">O SAS não corresponde</string>
<string name="sas_error_m_unexpected_message">A sessão recebeu uma mensagem inesperada</string>
@ -1881,8 +1881,8 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
\n
\nSe você não quiser ver mais conteúdo deste usuário, você pode bloqueá-lo para esconder suas mensagens</string>
<string name="permissions_rationale_msg_keys_backup_export">Element precisa de permissão para salvar suas chaves E2E no aparelho.
\n
<string name="permissions_rationale_msg_keys_backup_export">Element precisa de permissão para salvar suas chaves E2E no aparelho.
\n
\nPermita o acesso na próxima janela para poder exportar suas chaves manualmente.</string>
<string name="no_network_indicator">Não há conexão de rede no momento</string>
@ -1945,12 +1945,12 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="login_server_url_form_common_notice">Digite o endereço do servidor que você deseja usar</string>
<string name="login_sso_error_message">Ocorreu um erro ao carregar a página: %1$s (%2$d)</string>
<string name="login_mode_not_supported">O aplicativo não pode entrar neste servidor. O servidor suporta os seguintes tipos de login: %1$s.
\n
<string name="login_mode_not_supported">O aplicativo não pode entrar neste servidor. O servidor suporta os seguintes tipos de login: %1$s.
\n
\nDeseja entrar no servidor usando o Element Web\?</string>
<string name="login_registration_disabled">Desculpe, este servidor não está aceitando novas contas.</string>
<string name="login_registration_not_supported">O aplicativo não pode criar uma conta neste servidor.
\n
<string name="login_registration_not_supported">O aplicativo não pode criar uma conta neste servidor.
\n
\nDeseja criar uma conta no servidor usando o Element Web\?</string>
<string name="login_login_with_email_error">Este e-mail não está associado a nenhuma conta.</string>
@ -1978,8 +1978,8 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="login_reset_password_success_submit">Voltar para Entrar</string>
<string name="login_reset_password_cancel_confirmation_title">Atenção</string>
<string name="login_reset_password_cancel_confirmation_content">Sua senha ainda não foi alterada.
\n
<string name="login_reset_password_cancel_confirmation_content">Sua senha ainda não foi alterada.
\n
\nInterromper a alteração de senha\?</string>
<string name="login_set_email_title">Defina um endereço de e-mail</string>
@ -2023,7 +2023,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="login_terms_title">Aceitar termos para continuar</string>
<string name="login_wait_for_email_title">Por favor, verifique seu e-mail</string>
<string name="login_wait_for_email_notice">Acabamos de enviar um e-mail para %1$s.
<string name="login_wait_for_email_notice">Acabamos de enviar um e-mail para %1$s.
\nPor favor, clique no link que ele contém para continuar a criação da conta.</string>
<string name="login_validation_code_is_not_correct">O código digitado não está correto. Por favor, verifique-o.</string>
<string name="login_error_outdated_homeserver_title">Servidor desatualizado</string>
@ -2062,13 +2062,13 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="soft_logout_signin_submit">Entrar</string>
<string name="soft_logout_signin_password_hint">Senha</string>
<string name="soft_logout_clear_data_title">Limpar dados pessoais</string>
<string name="soft_logout_clear_data_notice">Atenção: Seus dados pessoais (incluindo chaves de criptografia) ainda estão armazenados neste aparelho.
\n
<string name="soft_logout_clear_data_notice">Atenção: Seus dados pessoais (incluindo chaves de criptografia) ainda estão armazenados neste aparelho.
\n
\nApague-os quando não usar mais este aparelho, ou se quiser entrar em outra conta.</string>
<string name="soft_logout_clear_data_submit">Limpar todos os dados</string>
<string name="soft_logout_clear_data_dialog_title">Limpar dados</string>
<string name="soft_logout_clear_data_dialog_content">Limpar todos os dados atualmente armazenados neste aparelho\?
<string name="soft_logout_clear_data_dialog_content">Limpar todos os dados atualmente armazenados neste aparelho\?
\nEntre novamente para acessar os dados e mensagens da sua conta.</string>
<string name="soft_logout_clear_data_dialog_e2e_warning_content">Você perderá o acesso as mensagens seguras a menos que você faça login para recuperar suas chaves de criptografia.</string>
<string name="soft_logout_clear_data_dialog_submit">Limpar dados</string>
@ -2113,11 +2113,11 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="verification_green_shield">Procure o escudo verde para garantir que um usuário seja confiável. Confie em todos os usuários numa sala para garantir que a sala é segura.</string>
<string name="verification_conclusion_not_secure">Não seguro</string>
<string name="verification_conclusion_compromised">Um dos seguintes casos pode estar comprometido:
\n
\n·- Seu servidor doméstico
\n·- O servidor do usuário que você está verificando
\n·- A sua, ou a conexão de outros usuários à internet
<string name="verification_conclusion_compromised">Um dos seguintes casos pode estar comprometido:
\n
\n·- Seu servidor doméstico
\n·- O servidor do usuário que você está verificando
\n·- A sua, ou a conexão de outros usuários à internet
\n·- O seu, ou o aparelho dos outros usuários</string>
<string name="sent_a_video">Vídeo.</string>
@ -2153,8 +2153,8 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="verification_verify_user">Confirmar %s</string>
<string name="verification_verified_user">Confirmou %s</string>
<string name="verification_request_waiting_for">Aguardando por %s…</string>
<string name="verification_request_alert_description">Para segurança extra, confirme %s comparando um código único em ambos os aparelhos.
\n
<string name="verification_request_alert_description">Para segurança extra, confirme %s comparando um código único em ambos os aparelhos.
\n
\nPara máxima segurança, faça isso pessoalmente.</string>
<string name="room_profile_not_encrypted_subtitle">As mensagens nesta sala não estão criptografadas de ponta a ponta.</string>
<string name="room_profile_encrypted_subtitle">As mensagens nesta sala estão criptografadas de ponta a ponta.
@ -2193,7 +2193,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="unignore">Desbloquear</string>
<string name="verify_cannot_cross_sign">Esta sessão não pode compartilhar essa confirmação com suas outras sessões.
<string name="verify_cannot_cross_sign">Esta sessão não pode compartilhar essa confirmação com suas outras sessões.
\nA confirmação será salvada localmente e será compartilhada em uma versão futura do aplicativo.</string>
<string name="room_list_sharing_header_recent_rooms">Salas recentes</string>
@ -2337,13 +2337,13 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="verify_cancel_self_verification_from_trusted">Se você cancelar, você não poderá ler mensagens criptografadas em seu novo aparelho, e outros usuários não confiarão nele</string>
<string name="verify_cancel_other">Você não confirmará %1$s (%2$s) se cancelar agora. Precisará começar novamente no perfil dele.</string>
<string name="verify_not_me_self_verification">Um dos seguintes casos pode estar comprometido:
\n
\n- Sua senha
\n- Seu servidor doméstico
\n- Este aparelho, ou o outro aparelho
\n- A conexão à internet de qualquer um dos aparelhos que você está usando
\n
<string name="verify_not_me_self_verification">Um dos seguintes casos pode estar comprometido:
\n
\n- Sua senha
\n- Seu servidor doméstico
\n- Este aparelho, ou o outro aparelho
\n- A conexão à internet de qualquer um dos aparelhos que você está usando
\n
\nRecomendamos que você altere imediatamente a sua senha e a chave de recuperação em Configurações.</string>
<string name="verify_cancelled_notice">Confirme seus aparelhos em Configurações.</string>
@ -2466,7 +2466,7 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="change_password_summary">Definir uma nova senha da conta…</string>
<string name="use_other_session_content_description">Use o Element mais recente em seus outros aparelhos: Element Web, Element para Computador, Element para iOS, Element para Android, ou outro cliente Matrix capaz de fazer autoverificação</string>
<string name="app_desktop_web">Element Web
<string name="app_desktop_web">Element Web
\nElement para Computador</string>
<string name="app_ios_android">Element para iOS
\nElement para Android</string>
@ -2495,8 +2495,8 @@ Na próxima tela, você será solicitado a permitir que o Element funcione sempr
<string name="error_empty_field_choose_user_name">Escolha um nome de usuário.</string>
<string name="error_empty_field_choose_password">Escolha uma senha.</string>
<string name="external_link_confirmation_title">Verificar este link</string>
<string name="external_link_confirmation_message">O link %1$s redirecionará você para outro site: %2$s.
\n
<string name="external_link_confirmation_message">O link %1$s redirecionará você para outro site: %2$s.
\n
\nDeseja continuar\?</string>
<string name="create_room_dm_failure">Não foi possível criar sua DM. Por favor, verifique os usuários que você deseja convidar e tente novamente.</string>

View File

@ -1685,7 +1685,7 @@
<string name="settings_discovery_category">Обнаружение</string>
<string name="settings_call_ringtone_use_default_stun_sum">Будет использовать%s в качестве помощника, если ваш домашний сервер не предлагает его (ваш IP-адрес будет доступен во время разговора)</string>
<string name="invite_no_identity_server_error">Добавьте идентификационный сервер в свои настройки, чтобы выполнить это действие.</string>
<string name="settings_background_fdroid_sync_mode">Режим фоновой синхронизации (Экспериментальный)</string>
<string name="settings_background_fdroid_sync_mode">Режим фоновой синхронизации</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element будет синхронизироваться в фоновом режиме таким образом, чтобы сохранить ограниченные ресурсы устройства (батарея).
\nВ зависимости от состояния ресурса вашего устройства, синхронизация может быть отложена операционной системой.</string>
<string name="settings_background_fdroid_sync_mode_real_time_description">Element будет синхронизироваться в фоновом режиме периодически в точное время (настраивается).
@ -2716,4 +2716,18 @@
<string name="error_opening_banned_room">Невозможно открыть комнату, в которую вам запрещён доступ.</string>
<string name="room_error_not_found">Невозможно найти эту комнату. Убедитесь, что она существует.</string>
<plurals name="seconds">
<item quantity="one">%d секунда</item>
<item quantity="few">%d секунды</item>
<item quantity="many">%d секунд</item>
</plurals>
<string name="settings_show_room_member_state_events">Показать события статуса участников комнаты</string>
<string name="settings_show_room_member_state_events_summary">Включает в себя события приглашения/ присоединения/выхода/исключения/бана и изменение аватара/отображаемого имени.</string>
<string name="sent_a_poll">Голосование</string>
<string name="sent_a_bot_buttons">Кнопки бота</string>
<string name="sent_a_reaction">Отреагировал: %s</string>
<string name="sent_verification_conclusion">Результат проверки</string>
<string name="universal_link_malformed">Ссылка была искажена</string>
</resources>

View File

@ -346,11 +346,11 @@
<string name="permissions_rationale_msg_camera">Element behöver tillstånd att komma åt din kamera för att kunna ta bilder och hålla videosamtal.</string>
<string name="permissions_rationale_msg_camera_explanation">"
\n
\nVänligen ge tillstånd i nästa popup för att kunna utföra samtalet."</string>
\nVänligen ge åtkomst i nästa popup för att kunna utföra samtalet."</string>
<string name="permissions_rationale_msg_record_audio">Element behöver tillstånd att komma åt din mikrofon för hålla röstsamtal.</string>
<string name="permissions_rationale_msg_record_audio_explanation">"
\n
\nVänligen ge tillstånd i nästa popup för att kunna utföra samtalet."</string>
\nVänligen ge åtkomst i nästa popup för att kunna utföra samtalet."</string>
<string name="permissions_rationale_msg_camera_and_audio">Element behöver tillstånd att komma åt din kamera och mikrofon för att kunna utföra videosamtal.
\n
\nVänligen ge tillstånd i nästa popup för att kunna utföra samtalet.</string>
@ -525,7 +525,7 @@
<string name="settings_add_3pid_confirm_password_title">Bekräfta ditt lösenord</string>
<string name="settings_containing_my_display_name">Meddelanden som innehåller mitt visningsnamn</string>
<string name="settings_user_settings">Användarinställningar</string>
<string name="settings_show_avatar_display_name_changes_messages_summary">Innehåller ändringar av avatar eller visningsnamn.</string>
<string name="settings_show_avatar_display_name_changes_messages_summary">Innehåller byten av avatar eller visningsnamn.</string>
<string name="devices_delete_dialog_text">Den här handlingen kräver extra autentisering.
\nFör att fortsätta, skriv in ditt lösenord.</string>
<string name="devices_delete_pswd">Lösenord:</string>
@ -1294,7 +1294,7 @@
<string name="settings_notification_privacy_no_background_sync">Apparna behöver <b>inte</b> ansluta till hemservern i bakgrunden, det bör reducera batterianvändning</string>
<string name="settings_turn_screen_on">Tänd skärmen i 3 sekunder</string>
<string name="settings_background_sync">Bakgrundssynkronisering</string>
<string name="settings_background_fdroid_sync_mode">Bakgrundssynkläge (experimentellt)</string>
<string name="settings_background_fdroid_sync_mode">Bakgrundssynkläge</string>
<string name="settings_background_fdroid_sync_mode_battery">Optimerad för batteri</string>
<string name="settings_app_term_conditions">Användarvillkor</string>
<string name="settings_third_party_notices">Meddelanden från tredje part</string>
@ -2125,7 +2125,7 @@
<string name="login_server_url_form_modular_hint">Element Matrix Services-adress</string>
<string name="login_server_url_form_other_hint">Adress</string>
<string name="login_server_url_form_modular_text">Premiumservervärd för organisationer</string>
<string name="login_server_url_form_modular_notice">Skriv in adressen för Modular Element eller servern du vill använda</string>
<string name="login_server_url_form_modular_notice">Skriv in adressen för den Modular Element eller server du vill använda</string>
<string name="login_server_url_form_other_notice">Skriv in adressen för en server eller Element du vill ansluta till</string>
<string name="login_server_url_form_common_notice">Skriv in adressen för servern du vill använda</string>
@ -2547,4 +2547,17 @@
<string name="error_opening_banned_room">Kan inte öppna ett rum du är bannad från.</string>
<string name="room_error_not_found">Kan inte hitta det här rummet. Se till att det existerar.</string>
<plurals name="seconds">
<item quantity="one">%d sekund</item>
<item quantity="other">%d sekunder</item>
</plurals>
<string name="settings_show_room_member_state_events">Visa statushändelser angående rumsmedlemmar</string>
<string name="settings_show_room_member_state_events_summary">Inkluderar när personer bjuds in, går med, lämnar, kickas, bannas eller byter sin avatar eller sitt namn.</string>
<string name="sent_a_poll">Omröstning</string>
<string name="sent_a_bot_buttons">Bottknappar</string>
<string name="sent_a_reaction">Reagerade med: %s</string>
<string name="sent_verification_conclusion">Verifieringsavslutning</string>
<string name="universal_link_malformed">Länken var felformaterad</string>
</resources>

View File

@ -1539,7 +1539,7 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意
<string name="settings_call_ringtone_use_default_stun">允許汰退呼叫協助伺服器</string>
<string name="settings_call_ringtone_use_default_stun_sum">當您的家伺服器未提供時,將會使用 %s 做為協助(您的 IP 位置將會在通話時被分享)</string>
<string name="invite_no_identity_server_error">在您的設定中新增一臺身份識別伺服器以執行此動作。</string>
<string name="settings_background_fdroid_sync_mode">背景同步模式(實驗性)</string>
<string name="settings_background_fdroid_sync_mode">背景同步模式</string>
<string name="settings_background_fdroid_sync_mode_battery">為電池最佳化</string>
<string name="settings_background_fdroid_sync_mode_battery_description">Element 將會在背景同步以節省裝置的有限資源(電池)。
\n取決於您裝置的資源狀態作業系統可能會延遲同步。</string>
@ -2562,4 +2562,16 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意
<string name="error_opening_banned_room">無法開啟禁止您進入的聊天室。</string>
<string name="room_error_not_found">找不到此聊天室。請確定它存在。</string>
<plurals name="seconds">
<item quantity="other">%d 秒</item>
</plurals>
<string name="settings_show_room_member_state_events">顯示聊天室成員狀態活動</string>
<string name="settings_show_room_member_state_events_summary">包含邀請/加入/離開/踢除/封鎖事件與大頭貼/顯示名稱變更等。</string>
<string name="sent_a_poll">投票</string>
<string name="sent_a_bot_buttons">機器人按鈕</string>
<string name="sent_a_reaction">反應:%s</string>
<string name="sent_verification_conclusion">驗證結論</string>
<string name="universal_link_malformed">連結格式錯誤</string>
</resources>

View File

@ -178,7 +178,7 @@
<string name="no_room_placeholder">No rooms</string>
<string name="no_public_room_placeholder">No public rooms available</string>
<plurals name="public_room_nb_users">
<item quantity="one">1 user</item>
<item quantity="one">%d user</item>
<item quantity="other">%d users</item>
</plurals>
@ -347,7 +347,7 @@
<!-- Mels -->
<plurals name="membership_changes">
<item quantity="one">1 membership change</item>
<item quantity="one">%d membership change</item>
<item quantity="other">%d membership changes</item>
</plurals>
@ -450,30 +450,30 @@
<string name="room_creation_title">New Chat</string>
<string name="room_creation_add_member">Add member</string>
<plurals name="room_header_active_members_count">
<item quantity="one">1 active members</item>
<item quantity="one">%d active members</item>
<item quantity="other">%d active members</item>
</plurals>
<plurals name="room_title_members">
<item quantity="one">1 member</item>
<item quantity="one">%d member</item>
<item quantity="other">%d members</item>
</plurals>
<string name="room_title_one_member">1 member</string>
<!-- Time -->
<plurals name="format_time_s">
<item quantity="one">1s</item>
<item quantity="one">%ds</item>
<item quantity="other">%ds</item>
</plurals>
<plurals name="format_time_m">
<item quantity="one">1m</item>
<item quantity="one">%dm</item>
<item quantity="other">%dm</item>
</plurals>
<plurals name="format_time_h">
<item quantity="one">1h</item>
<item quantity="one">%dh</item>
<item quantity="other">%dh</item>
</plurals>
<plurals name="format_time_d">
<item quantity="one">1d</item>
<item quantity="one">%dd</item>
<item quantity="other">%dd</item>
</plurals>
@ -581,7 +581,7 @@
<string name="room_message_file_not_found">File not found</string>
<string name="room_do_not_have_permission_to_post">You do not have permission to post to this room</string>
<plurals name="room_new_messages_notification">
<item quantity="one">1 new message</item>
<item quantity="one">%d new message</item>
<item quantity="other">%d new messages</item>
</plurals>
@ -604,7 +604,7 @@
<string name="room_details_files">Files</string>
<string name="room_details_settings">Settings</string>
<plurals name="room_details_selected">
<item quantity="one">1 selected</item>
<item quantity="one">%d selected</item>
<item quantity="other">%d selected</item>
</plurals>
<string name="malformed_id">Malformed ID. Should be an email address or a Matrix ID like \'@localpart:domain\'</string>
@ -642,7 +642,7 @@
<!-- Directory -->
<string name="directory_search_results_title">Browse directory</string>
<plurals name="directory_search_rooms">
<item quantity="one">1 room</item>
<item quantity="one">%d room</item>
<item quantity="other">%d rooms</item>
</plurals>
<plurals name="directory_search_rooms_for">
@ -1143,20 +1143,24 @@
<!-- Notifications -->
<plurals name="notification_unread_notified_messages">
<item quantity="one">1 unread notified message</item>
<item quantity="one">%d unread notified message</item>
<item quantity="other">%d unread notified messages</item>
</plurals>
<plurals name="notification_unread_notified_messages_in_room_msgs">
<item quantity="one">1 unread notified message</item>
<item quantity="one">%d unread notified message</item>
<item quantity="other">%d unread notified messages</item>
</plurals>
<plurals name="notification_unread_notified_messages_in_room_rooms">
<item quantity="one">1 room</item>
<item quantity="one">%d room</item>
<item quantity="other">%d rooms</item>
</plurals>
<plurals name="notification_invitations">
<item quantity="one">%d invitation</item>
<item quantity="other">%d invitations</item>
</plurals>
<plurals name="notification_compat_summary_line_for_room">
<item quantity="one">%1$s: 1 message</item>
<item quantity="one">%1$s: %2$d message</item>
<item quantity="other">%1$s: %2$d messages</item>
</plurals>
<plurals name="notification_compat_summary_title">
@ -1165,6 +1169,8 @@
</plurals>
<string name="notification_unread_notified_messages_in_room">%1$s in %2$s"</string>
<string name="notification_unread_notified_messages_in_room_and_invitation">%1$s in %2$s and %3$s"</string>
<string name="notification_unread_notified_messages_and_invitation">%1$s and %2$s"</string>
<string name="notification_unknown_new_event">New Event</string>
<string name="notification_unknown_room_name">Room</string>
<string name="notification_new_messages">New Messages</string>
@ -1193,7 +1199,7 @@
<string name="settings_labs_create_conference_with_jitsi">Create conference calls with jitsi</string>
<string name="widget_delete_message_confirmation">Are you sure you want to delete the widget from this room?</string>
<plurals name="active_widgets">
<item quantity="one">1 active widget</item>
<item quantity="one">%d active widget</item>
<item quantity="other">%d active widgets</item>
</plurals>
<string name="active_widget_view_action">"VIEW"</string>
@ -1319,12 +1325,12 @@
<string name="filter_group_rooms">Filter group rooms</string>
<plurals name="group_members">
<item quantity="one">1 member</item>
<item quantity="one">%d member</item>
<item quantity="other">%d members</item>
</plurals>
<plurals name="group_rooms">
<item quantity="one">1 room</item>
<item quantity="one">%d room</item>
<item quantity="other">%d rooms</item>
</plurals>
<string name="group_no_long_description">The community admin has not provided a long description for this community.</string>
@ -1809,7 +1815,7 @@
<string name="two_users_read">%1$s and %2$s read</string>
<string name="one_user_read">%s read</string>
<plurals name="fallback_users_read">
<item quantity="one">1 user read</item>
<item quantity="one">%d user read</item>
<item quantity="other">%d users read</item>
</plurals>
@ -1988,6 +1994,7 @@
<string name="login_validation_code_is_not_correct">The entered code is not correct. Please check.</string>
<string name="login_error_outdated_homeserver_title">Outdated homeserver</string>
<string name="login_error_outdated_homeserver_content">This homeserver is running too old a version to connect to. Ask your homeserver admin to upgrade.</string>
<string name="login_error_outdated_homeserver_warning_content">This homeserver is running an old version. Ask your homeserver admin to upgrade. You can continue, but some features may not work correctly.</string>
<plurals name="login_error_limit_exceeded_retry_after">
<item quantity="one">Too many requests have been sent. You can retry in %1$d second…</item>
@ -2559,14 +2566,26 @@
<string name="too_many_pin_failures">Too many errors, you\'ve been logged out</string>
<string name="create_pin_title">Choose a PIN for security</string>
<string name="create_pin_confirm_title">Confirm PIN</string>
<string name="create_pin_confirm_failure">Failed to validate pin, please tap a new one.</string>
<string name="create_pin_confirm_failure">Failed to validate PIN, please tap a new one.</string>
<string name="auth_pin_title">Enter your PIN</string>
<string name="auth_pin_forgot">Forgot PIN?</string>
<string name="auth_pin_reset_title">Reset pin</string>
<string name="auth_pin_new_pin_action">New pin</string>
<string name="auth_pin_reset_title">Reset PIN</string>
<string name="auth_pin_new_pin_action">New PIN</string>
<string name="auth_pin_reset_content">To reset your PIN, you\'ll need to re-login and create a new one.</string>
<string name="settings_security_application_protection_title">Protect access</string>
<string name="settings_security_application_protection_summary">Protect access using PIN and biometrics.</string>
<string name="settings_security_application_protection_screen_title">Configure protection</string>
<string name="settings_security_pin_code_title">Enable PIN</string>
<string name="settings_security_pin_code_summary">If you want to reset your PIN, tap Forgot PIN to logout and reset.</string>
<string name="settings_security_pin_code_use_biometrics_title">Enable biometrics</string>
<string name="settings_security_pin_code_use_biometrics_summary_on">Enable device specific biometrics, like fingerprints and face recognition.</string>
<string name="settings_security_pin_code_use_biometrics_summary_off">PIN code is the only way to unlock Element.</string>
<string name="settings_security_pin_code_notifications_title">Show content in notifications</string>
<string name="settings_security_pin_code_notifications_summary_on">Show details like room names and message content.</string>
<string name="settings_security_pin_code_notifications_summary_off">Only display number of unread messages in a simple notification.</string>
<string name="settings_security_pin_code_grace_period_title">Require PIN after 2 minutes</string>
<string name="settings_security_pin_code_grace_period_summary_on">PIN code is required after 2 minutes of not using Element.</string>
<string name="settings_security_pin_code_grace_period_summary_off">PIN code is required every time you open Element.</string>
<string name="auth_pin_confirm_to_disable_title">Confirm PIN to disable PIN</string>
<string name="error_opening_banned_room">Can\'t open a room where you are banned from.</string>
<string name="room_error_not_found">Can\'t find this room. Make sure it exists.</string>

View File

@ -8,35 +8,40 @@
<item name="android:paddingEnd">36dp</item>
</style>
<item name="loginLogo" type="id" />
<item name="loginFormScrollView" type="id" />
<item name="loginFormContainer" type="id" />
<style name="LoginLogo">
<item name="android:id">@id/loginLogo</item>
<item name="android:layout_width">60dp</item>
<item name="android:layout_height">60dp</item>
<item name="android:layout_gravity">center_horizontal</item>
<item name="android:layout_marginBottom">36dp</item>
<item name="android:src">@drawable/element_logo_green</item>
<item name="android:transitionName">loginLogoTransition</item>
<item name="android:importantForAccessibility">no</item>
</style>
<style name="LoginFormContainer">
<item name="android:id">@id/loginFormContainer</item>
<item name="android:paddingTop">36dp</item>
<item name="android:paddingStart">36dp</item>
<item name="android:paddingEnd">36dp</item>
<item name="android:orientation">vertical</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="LoginFormScrollViewBase">
<style name="LoginFormScrollView">
<item name="android:id">@id/loginFormScrollView</item>
<item name="layout_constraintEnd_toEndOf">parent</item>
<item name="layout_constraintBottom_toBottomOf">parent</item>
<item name="android:layout_width">0dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:clipToPadding">false</item>
<item name="android:paddingTop">32dp</item>
<item name="android:paddingBottom">32dp</item>
</style>
<style name="LoginFormScrollView" parent="LoginFormScrollViewBase">
<item name="layout_constraintTop_toTopOf">parent</item>
<item name="layout_constraintStart_toStartOf">parent</item>
<item name="android:layout_height">0dp</item>
<item name="android:layout_marginTop">72dp</item>
</style>
<style name="Style.Vector.Login.Button" parent="VectorButtonStyle">
<item name="android:minHeight">52dp</item>
<item name="android:textAllCaps">false</item>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
android:summary="@string/settings_security_pin_code_summary"
android:title="@string/settings_security_pin_code_title" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="true"
android:dependency="SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
android:key="SETTINGS_SECURITY_USE_BIOMETRICS_FLAG"
android:summaryOff="@string/settings_security_pin_code_use_biometrics_summary_off"
android:summaryOn="@string/settings_security_pin_code_use_biometrics_summary_on"
android:title="@string/settings_security_pin_code_use_biometrics_title" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="true"
android:dependency="SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
android:key="SETTINGS_SECURITY_USE_COMPLETE_NOTIFICATIONS_FLAG"
android:summaryOff="@string/settings_security_pin_code_notifications_summary_off"
android:summaryOn="@string/settings_security_pin_code_notifications_summary_on"
android:title="@string/settings_security_pin_code_notifications_title" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="true"
android:dependency="SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
android:key="SETTINGS_SECURITY_USE_GRACE_PERIOD_FLAG"
android:summaryOff="@string/settings_security_pin_code_grace_period_summary_off"
android:summaryOn="@string/settings_security_pin_code_grace_period_summary_on"
android:title="@string/settings_security_pin_code_grace_period_title" />
</androidx.preference.PreferenceScreen>

View File

@ -56,8 +56,7 @@
<im.vector.app.core.preference.VectorSwitchPreference
android:key="SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
android:title="@string/settings_always_show_timestamps"
app:isPreferenceVisible="@bool/false_not_implemented" />
android:title="@string/settings_always_show_timestamps" />
<im.vector.app.core.preference.VectorSwitchPreference
android:key="SETTINGS_12_24_TIMESTAMPS_KEY"

View File

@ -117,18 +117,18 @@
<im.vector.app.core.preference.VectorPreferenceCategory android:title="@string/settings_other">
<im.vector.app.core.preference.VectorPreference
android:key="SETTINGS_SECURITY_PIN"
android:persistent="false"
android:summary="@string/settings_security_application_protection_summary"
android:title="@string/settings_security_application_protection_title" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_SECURITY_USE_FLAG_SECURE"
android:summary="@string/settings_security_prevent_screenshots_summary"
android:title="@string/settings_security_prevent_screenshots_title" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_SECURITY_USE_PIN_CODE_FLAG"
android:summary="@string/settings_security_pin_code_summary"
android:title="@string/settings_security_pin_code_title" />
</im.vector.app.core.preference.VectorPreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -17,7 +17,7 @@
package im.vector.app.features.home
import im.vector.app.R
import im.vector.app.core.utils.getColorFromUserId
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider.Companion.getColorFromUserId
import org.junit.Assert.assertEquals
import org.junit.Test