Merge branch 'release/0.91.4'
This commit is contained in:
commit
51abdb6066
21
CHANGES.md
21
CHANGES.md
|
@ -1,3 +1,24 @@
|
||||||
|
Changes in Riot.imX 0.91.4 (2020-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Re-activate Wellknown support with updated UI (#1614)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Upload device keys only once to the homeserver and fix crash when no network (#1629)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix crash when coming from a notification (#1601)
|
||||||
|
- Fix Exception when importing keys (#1576)
|
||||||
|
- File isn't downloaded when another file with the same name already exists (#1578)
|
||||||
|
- saved images don't show up in gallery (#1324)
|
||||||
|
- Fix reply fallback leaking sender locale (#429)
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Fix lint false-positive about WorkManager (#1012)
|
||||||
|
- Upgrade build-tools from 3.5.3 to 3.6.6
|
||||||
|
- Upgrade gradle from 5.4.1 to 5.6.4
|
||||||
|
|
||||||
Changes in Riot.imX 0.91.3 (2020-07-01)
|
Changes in Riot.imX 0.91.3 (2020-07-01)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||||
classpath 'com.google.gms:google-services:4.3.2'
|
classpath 'com.google.gms:google-services:4.3.2'
|
||||||
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
classpath "com.airbnb.okreplay:gradle-plugin:1.5.0"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Fri Sep 27 10:10:35 CEST 2019
|
#Thu Jul 02 12:33:07 CEST 2020
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
|
|
|
@ -64,3 +64,19 @@
|
||||||
|
|
||||||
### Webrtc
|
### Webrtc
|
||||||
-keep class org.webrtc.** { *; }
|
-keep class org.webrtc.** { *; }
|
||||||
|
|
||||||
|
### Serializable persisted classes
|
||||||
|
# https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
|
||||||
|
-keepnames class * implements java.io.Serializable
|
||||||
|
|
||||||
|
-keepclassmembers class * implements java.io.Serializable {
|
||||||
|
static final long serialVersionUID;
|
||||||
|
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||||
|
!static !transient <fields>;
|
||||||
|
!private <fields>;
|
||||||
|
!private <methods>;
|
||||||
|
private void writeObject(java.io.ObjectOutputStream);
|
||||||
|
private void readObject(java.io.ObjectInputStream);
|
||||||
|
java.lang.Object writeReplace();
|
||||||
|
java.lang.Object readResolve();
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="im.vector.matrix.android">
|
package="im.vector.matrix.android">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
@ -8,11 +7,6 @@
|
||||||
|
|
||||||
<application android:networkSecurityConfig="@xml/network_security_config">
|
<application android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
<provider
|
|
||||||
android:name="androidx.work.impl.WorkManagerInitializer"
|
|
||||||
android:authorities="${applicationId}.workmanager-init"
|
|
||||||
android:exported="false"
|
|
||||||
tools:node="remove" />
|
|
||||||
<!--
|
<!--
|
||||||
The SDK offers a secured File provider to access downloaded files.
|
The SDK offers a secured File provider to access downloaded files.
|
||||||
Access to these file will be given via the FileService, with a temporary
|
Access to these file will be given via the FileService, with a temporary
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.auth.login
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.internal.auth.AuthAPI
|
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
|
@ -27,6 +28,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||||
|
import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -49,13 +51,28 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||||
|
|
||||||
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||||
val client = buildClient(params.homeServerConnectionConfig)
|
val client = buildClient(params.homeServerConnectionConfig)
|
||||||
val authAPI = retrofitFactory.create(client, params.homeServerConnectionConfig.homeServerUri.toString())
|
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
|
||||||
|
|
||||||
|
val authAPI = retrofitFactory.create(client, homeServerUrl)
|
||||||
.create(AuthAPI::class.java)
|
.create(AuthAPI::class.java)
|
||||||
|
|
||||||
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||||
|
|
||||||
val credentials = executeRequest<Credentials>(null) {
|
val credentials = try {
|
||||||
apiCall = authAPI.login(loginParams)
|
executeRequest<Credentials>(null) {
|
||||||
|
apiCall = authAPI.login(loginParams)
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
when (throwable) {
|
||||||
|
is UnrecognizedCertificateException -> {
|
||||||
|
throw Failure.UnrecognizedCertificateFailure(
|
||||||
|
homeServerUrl,
|
||||||
|
throwable.fingerprint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
||||||
|
|
|
@ -70,7 +70,6 @@ import im.vector.matrix.android.internal.crypto.model.event.RoomKeyWithHeldConte
|
||||||
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
|
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
|
||||||
import im.vector.matrix.android.internal.crypto.model.toRest
|
import im.vector.matrix.android.internal.crypto.model.toRest
|
||||||
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||||
|
@ -98,6 +97,7 @@ import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
import im.vector.matrix.android.internal.task.TaskThread
|
import im.vector.matrix.android.internal.task.TaskThread
|
||||||
import im.vector.matrix.android.internal.task.configureWith
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.fetchCopied
|
import im.vector.matrix.android.internal.util.fetchCopied
|
||||||
|
@ -340,11 +340,14 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureDevice() {
|
fun ensureDevice() {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
|
||||||
// Open the store
|
// Open the store
|
||||||
cryptoStore.open()
|
cryptoStore.open()
|
||||||
// TODO why do that everytime? we should mark that it was done
|
// this can throw if no network
|
||||||
uploadDeviceKeys()
|
tryThis {
|
||||||
|
uploadDeviceKeys()
|
||||||
|
}
|
||||||
|
|
||||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||||
// this can throw if no backup
|
// this can throw if no backup
|
||||||
tryThis {
|
tryThis {
|
||||||
|
@ -389,7 +392,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
// } else {
|
// } else {
|
||||||
|
|
||||||
// Why would we do that? it will be called at end of syn
|
// Why would we do that? it will be called at end of syn
|
||||||
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
||||||
// }
|
// }
|
||||||
}.fold(
|
}.fold(
|
||||||
{
|
{
|
||||||
|
@ -888,7 +891,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
*/
|
*/
|
||||||
private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
|
private fun handleSDKLevelGossip(secretName: String?, secretValue: String): Boolean {
|
||||||
return when (secretName) {
|
return when (secretName) {
|
||||||
MASTER_KEY_SSSS_NAME -> {
|
MASTER_KEY_SSSS_NAME -> {
|
||||||
crossSigningService.onSecretMSKGossip(secretValue)
|
crossSigningService.onSecretMSKGossip(secretValue)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -980,7 +983,11 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Upload my user's device keys.
|
* Upload my user's device keys.
|
||||||
*/
|
*/
|
||||||
private suspend fun uploadDeviceKeys(): KeysUploadResponse {
|
private suspend fun uploadDeviceKeys() {
|
||||||
|
if (cryptoStore.getDeviceKeysUploaded()) {
|
||||||
|
Timber.d("Keys already uploaded, nothing to do")
|
||||||
|
return
|
||||||
|
}
|
||||||
// Prepare the device keys data to send
|
// Prepare the device keys data to send
|
||||||
// Sign it
|
// Sign it
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
||||||
|
@ -991,7 +998,9 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null)
|
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null)
|
||||||
return uploadKeysTask.execute(uploadDeviceKeysParams)
|
uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||||
|
|
||||||
|
cryptoStore.setDeviceKeysUploaded(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -433,4 +433,7 @@ internal interface IMXCryptoStore {
|
||||||
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest?
|
||||||
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
fun getGossipingEventsTrail(): List<Event>
|
fun getGossipingEventsTrail(): List<Event>
|
||||||
|
|
||||||
|
fun setDeviceKeysUploaded(uploaded: Boolean)
|
||||||
|
fun getDeviceKeysUploaded(): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -842,6 +842,18 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setDeviceKeysUploaded(uploaded: Boolean) {
|
||||||
|
doRealmTransaction(realmConfiguration) {
|
||||||
|
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer = uploaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDeviceKeysUploaded(): Boolean {
|
||||||
|
return doWithRealm(realmConfiguration) {
|
||||||
|
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>) {
|
override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>) {
|
||||||
doRealmTransaction(realmConfiguration) {
|
doRealmTransaction(realmConfiguration) {
|
||||||
// Reset all
|
// Reset all
|
||||||
|
|
|
@ -54,7 +54,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
// 0, 1, 2: legacy Riot-Android
|
// 0, 1, 2: legacy Riot-Android
|
||||||
// 3: migrate to RiotX schema
|
// 3: migrate to RiotX schema
|
||||||
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
||||||
const val CRYPTO_STORE_SCHEMA_VERSION = 10L
|
const val CRYPTO_STORE_SCHEMA_VERSION = 11L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
@ -70,6 +70,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
if (oldVersion <= 7) migrateTo8(realm)
|
if (oldVersion <= 7) migrateTo8(realm)
|
||||||
if (oldVersion <= 8) migrateTo9(realm)
|
if (oldVersion <= 8) migrateTo9(realm)
|
||||||
if (oldVersion <= 9) migrateTo10(realm)
|
if (oldVersion <= 9) migrateTo10(realm)
|
||||||
|
if (oldVersion <= 10) migrateTo11(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1Legacy(realm: DynamicRealm) {
|
private fun migrateTo1Legacy(realm: DynamicRealm) {
|
||||||
|
@ -176,13 +177,14 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper2
|
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
|
||||||
realm.schema.get("OlmInboundGroupSessionEntity")
|
realm.schema.get("OlmInboundGroupSessionEntity")
|
||||||
?.transform { obj ->
|
?.transform { obj ->
|
||||||
try {
|
try {
|
||||||
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
|
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
|
||||||
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
|
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
|
||||||
val newOlmInboundGroupSessionWrapper2 = OlmInboundGroupSessionWrapper2()
|
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
|
||||||
|
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
|
||||||
.apply {
|
.apply {
|
||||||
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
|
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
|
||||||
roomId = mxOlmInboundGroupSession2.mRoomId
|
roomId = mxOlmInboundGroupSession2.mRoomId
|
||||||
|
@ -191,7 +193,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
|
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper2))
|
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Error")
|
Timber.e(e, "Error")
|
||||||
|
@ -445,4 +447,11 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
||||||
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
|
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
|
||||||
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
|
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
|
||||||
|
private fun migrateTo11(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 10 -> 11")
|
||||||
|
realm.schema.get("CryptoMetadataEntity")
|
||||||
|
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,9 @@ internal open class CryptoMetadataEntity(
|
||||||
// The keys backup version currently used. Null means no backup.
|
// The keys backup version currently used. Null means no backup.
|
||||||
var backupVersion: String? = null,
|
var backupVersion: String? = null,
|
||||||
|
|
||||||
|
// The device keys has been sent to the homeserver
|
||||||
|
var deviceKeysSentToServer: Boolean = false,
|
||||||
|
|
||||||
var xSignMasterPrivateKey: String? = null,
|
var xSignMasterPrivateKey: String? = null,
|
||||||
var xSignUserPrivateKey: String? = null,
|
var xSignUserPrivateKey: String? = null,
|
||||||
var xSignSelfSignedPrivateKey: String? = null,
|
var xSignSelfSignedPrivateKey: String? = null,
|
||||||
|
|
|
@ -177,7 +177,6 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.root.getClearContent().toModel())
|
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.root.getClearContent().toModel())
|
||||||
val replyFormatted = REPLY_PATTERN.format(
|
val replyFormatted = REPLY_PATTERN.format(
|
||||||
permalink,
|
permalink,
|
||||||
stringProvider.getString(R.string.message_reply_to_prefix),
|
|
||||||
userLink,
|
userLink,
|
||||||
originalEvent.senderInfo.disambiguatedDisplayName,
|
originalEvent.senderInfo.disambiguatedDisplayName,
|
||||||
body.takeFormatted(),
|
body.takeFormatted(),
|
||||||
|
@ -372,7 +371,6 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.root.getClearContent().toModel())
|
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.root.getClearContent().toModel())
|
||||||
val replyFormatted = REPLY_PATTERN.format(
|
val replyFormatted = REPLY_PATTERN.format(
|
||||||
permalink,
|
permalink,
|
||||||
stringProvider.getString(R.string.message_reply_to_prefix),
|
|
||||||
userLink,
|
userLink,
|
||||||
userId,
|
userId,
|
||||||
body.takeFormatted(),
|
body.takeFormatted(),
|
||||||
|
@ -434,10 +432,10 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
TextContent(content.body, formattedText)
|
TextContent(content.body, formattedText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageType.MSGTYPE_FILE -> return TextContent(stringProvider.getString(R.string.reply_to_a_file))
|
MessageType.MSGTYPE_FILE -> return TextContent("sent a file.")
|
||||||
MessageType.MSGTYPE_AUDIO -> return TextContent(stringProvider.getString(R.string.reply_to_an_audio_file))
|
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
|
||||||
MessageType.MSGTYPE_IMAGE -> return TextContent(stringProvider.getString(R.string.reply_to_an_image))
|
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
|
||||||
MessageType.MSGTYPE_VIDEO -> return TextContent(stringProvider.getString(R.string.reply_to_a_video))
|
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
|
||||||
else -> return TextContent(content?.body ?: "")
|
else -> return TextContent(content?.body ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -489,6 +487,6 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
// </blockquote>
|
// </blockquote>
|
||||||
// </mx-reply>
|
// </mx-reply>
|
||||||
// No whitespace because currently breaks temporary formatted text to Span
|
// No whitespace because currently breaks temporary formatted text to Span
|
||||||
const val REPLY_PATTERN = """<mx-reply><blockquote><a href="%s">%s</a><a href="%s">%s</a><br />%s</blockquote></mx-reply>%s"""
|
const val REPLY_PATTERN = """<mx-reply><blockquote><a href="%s">In reply to</a> <a href="%s">%s</a><br />%s</blockquote></mx-reply>%s"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||||
|
import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException
|
||||||
import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI
|
import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI
|
||||||
import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
import im.vector.matrix.android.internal.session.identity.IdentityAuthAPI
|
||||||
import im.vector.matrix.android.internal.task.Task
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
@ -106,6 +107,12 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
||||||
}
|
}
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
when (throwable) {
|
when (throwable) {
|
||||||
|
is UnrecognizedCertificateException -> {
|
||||||
|
throw Failure.UnrecognizedCertificateFailure(
|
||||||
|
"https://$domain",
|
||||||
|
throwable.fingerprint
|
||||||
|
)
|
||||||
|
}
|
||||||
is Failure.NetworkConnection -> {
|
is Failure.NetworkConnection -> {
|
||||||
WellknownResult.Ignore
|
WellknownResult.Ignore
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,12 +63,6 @@
|
||||||
<string name="summary_user_sent_sticker">أرسل %1$s ملصقا.</string>
|
<string name="summary_user_sent_sticker">أرسل %1$s ملصقا.</string>
|
||||||
|
|
||||||
<string name="notice_avatar_changed_too">(تغيّرت الصورة أيضا)</string>
|
<string name="notice_avatar_changed_too">(تغيّرت الصورة أيضا)</string>
|
||||||
<string name="message_reply_to_prefix">ردا على</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">أرسل صورة.</string>
|
|
||||||
<string name="reply_to_a_video">أرسل فديوهًا.</string>
|
|
||||||
<string name="reply_to_an_audio_file">أرسل ملف صوت.</string>
|
|
||||||
<string name="reply_to_a_file">أرسل ملفًا.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">دعوة من %s</string>
|
<string name="room_displayname_invite_from">دعوة من %s</string>
|
||||||
<string name="room_displayname_empty_room">غرفة فارغة</string>
|
<string name="room_displayname_empty_room">غرفة فارغة</string>
|
||||||
|
|
|
@ -52,8 +52,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Şifrəni aça bilmir: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Şifrəni aça bilmir: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Göndərənin cihazı bu mesaj üçün açarları bizə göndərməyib.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Cavab olaraq</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Redaktə etmək olmur</string>
|
<string name="could_not_redact">Redaktə etmək olmur</string>
|
||||||
<string name="unable_to_send_message">Mesaj göndərmək olmur</string>
|
<string name="unable_to_send_message">Mesaj göndərmək olmur</string>
|
||||||
|
|
||||||
|
@ -69,11 +67,6 @@
|
||||||
<string name="medium_email">Elektron poçt ünvanı</string>
|
<string name="medium_email">Elektron poçt ünvanı</string>
|
||||||
<string name="medium_phone_number">Telefon nömrəsi</string>
|
<string name="medium_phone_number">Telefon nömrəsi</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">şəkil göndərdi.</string>
|
|
||||||
<string name="reply_to_a_video">video göndərdi.</string>
|
|
||||||
<string name="reply_to_an_audio_file">səs faylı göndərdi.</string>
|
|
||||||
<string name="reply_to_a_file">fayl göndərdi.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">%s-dən dəvət</string>
|
<string name="room_displayname_invite_from">%s-dən dəvət</string>
|
||||||
<string name="room_displayname_room_invite">Otağa dəvət</string>
|
<string name="room_displayname_room_invite">Otağa dəvət</string>
|
||||||
|
|
||||||
|
|
|
@ -63,13 +63,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s изпрати стикер.</string>
|
<string name="summary_user_sent_sticker">%1$s изпрати стикер.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">В отговор на</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">изпрати снимка.</string>
|
|
||||||
<string name="reply_to_a_video">изпрати видео.</string>
|
|
||||||
<string name="reply_to_an_audio_file">изпрати аудио файл.</string>
|
|
||||||
<string name="reply_to_a_file">изпрати файл.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Покана от %s</string>
|
<string name="room_displayname_invite_from">Покана от %s</string>
|
||||||
<string name="room_displayname_room_invite">Покана за стая</string>
|
<string name="room_displayname_room_invite">Покана за стая</string>
|
||||||
<string name="room_displayname_two_members">%1$s и %2$s</string>
|
<string name="room_displayname_two_members">%1$s и %2$s</string>
|
||||||
|
|
|
@ -76,11 +76,4 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s ha enviat un adhesiu.</string>
|
<string name="summary_user_sent_sticker">%1$s ha enviat un adhesiu.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">En resposta a</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">ha enviat una imatge.</string>
|
|
||||||
<string name="reply_to_a_video">ha enviat un vídeo.</string>
|
|
||||||
<string name="reply_to_an_audio_file">ha enviat un fitxer d\'àudio.</string>
|
|
||||||
<string name="reply_to_a_file">ha enviat un fitxer.</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -46,8 +46,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Nelze dešifrovat: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Nelze dešifrovat: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Odesílatelovo zařízení neposlalo klíče pro tuto zprávu.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Odesílatelovo zařízení neposlalo klíče pro tuto zprávu.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">V odpovědi na</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Nelze vymazat</string>
|
<string name="could_not_redact">Nelze vymazat</string>
|
||||||
<string name="unable_to_send_message">Zprávu nelze odeslat</string>
|
<string name="unable_to_send_message">Zprávu nelze odeslat</string>
|
||||||
|
|
||||||
|
@ -63,11 +61,6 @@
|
||||||
<string name="medium_email">E-mailová adresa</string>
|
<string name="medium_email">E-mailová adresa</string>
|
||||||
<string name="medium_phone_number">Telefonní číslo</string>
|
<string name="medium_phone_number">Telefonní číslo</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">odeslal obrázek.</string>
|
|
||||||
<string name="reply_to_a_video">odeslal video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">odeslal zvukový soubor.</string>
|
|
||||||
<string name="reply_to_a_file">odeslal soubor.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Pozvání od %s</string>
|
<string name="room_displayname_invite_from">Pozvání od %s</string>
|
||||||
<string name="room_displayname_room_invite">Pozvání do místnosti</string>
|
<string name="room_displayname_room_invite">Pozvání do místnosti</string>
|
||||||
|
|
||||||
|
|
|
@ -73,13 +73,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s sandte einen Sticker.</string>
|
<string name="summary_user_sent_sticker">%1$s sandte einen Sticker.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Als Antwort auf</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">hat ein Bild gesendet.</string>
|
|
||||||
<string name="reply_to_a_video">hat ein Video gesendet.</string>
|
|
||||||
<string name="reply_to_an_audio_file">hat eine Audio-Datei gesendet.</string>
|
|
||||||
<string name="reply_to_a_file">sandte eine Datei.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Einladung von %s</string>
|
<string name="room_displayname_invite_from">Einladung von %s</string>
|
||||||
<string name="room_displayname_room_invite">Raumeinladung</string>
|
<string name="room_displayname_room_invite">Raumeinladung</string>
|
||||||
|
|
|
@ -40,8 +40,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Αδυναμία αποκρυπτογράφησης: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Αδυναμία αποκρυπτογράφησης: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Η συσκευή του/της αποστολέα δεν μας έχει στείλει τα κλειδιά για αυτό το μήνυμα.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Η συσκευή του/της αποστολέα δεν μας έχει στείλει τα κλειδιά για αυτό το μήνυμα.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Προς απάντηση στο</string>
|
|
||||||
|
|
||||||
<string name="unable_to_send_message">Αποτυχία αποστολής μηνύματος</string>
|
<string name="unable_to_send_message">Αποτυχία αποστολής μηνύματος</string>
|
||||||
|
|
||||||
<string name="message_failed_to_upload">Αποτυχία αναφόρτωσης εικόνας</string>
|
<string name="message_failed_to_upload">Αποτυχία αναφόρτωσης εικόνας</string>
|
||||||
|
@ -56,10 +54,6 @@
|
||||||
<string name="notice_voip_finished">Η VoIP διάσκεψη έληξε</string>
|
<string name="notice_voip_finished">Η VoIP διάσκεψη έληξε</string>
|
||||||
|
|
||||||
<string name="notice_room_join">Ο/Η %1$s εισήλθε στο δωμάτιο</string>
|
<string name="notice_room_join">Ο/Η %1$s εισήλθε στο δωμάτιο</string>
|
||||||
<string name="reply_to_an_image">έστειλε μία εικόνα.</string>
|
|
||||||
<string name="reply_to_a_video">έστειλε ένα βίντεο.</string>
|
|
||||||
<string name="reply_to_an_audio_file">έστειλε ένα αρχείο ήχου.</string>
|
|
||||||
<string name="reply_to_a_file">έστειλε ένα αρχείο.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Πρόσκληση από %s</string>
|
<string name="room_displayname_invite_from">Πρόσκληση από %s</string>
|
||||||
<string name="room_displayname_room_invite">Πρόσκληση στο δωμάτιο</string>
|
<string name="room_displayname_room_invite">Πρόσκληση στο δωμάτιο</string>
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Ne eblas malĉifri: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Ne eblas malĉifri: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">La aparato de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">La aparato de la sendanto ne sendis al ni la ŝlosilojn por tiu mesaĝo.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Responde al</string>
|
|
||||||
|
|
||||||
<string name="summary_message">%1$s: %2$s</string>
|
<string name="summary_message">%1$s: %2$s</string>
|
||||||
<string name="notice_display_name_set">%1$s ŝanĝis sian vidigan nomon al %2$s</string>
|
<string name="notice_display_name_set">%1$s ŝanĝis sian vidigan nomon al %2$s</string>
|
||||||
<string name="notice_display_name_changed_from">%1$s ŝanĝis sian vidigan nomon de %2$s al %3$s</string>
|
<string name="notice_display_name_changed_from">%1$s ŝanĝis sian vidigan nomon de %2$s al %3$s</string>
|
||||||
|
@ -62,11 +60,6 @@
|
||||||
<string name="medium_email">Retpoŝtadreso</string>
|
<string name="medium_email">Retpoŝtadreso</string>
|
||||||
<string name="medium_phone_number">Telefonnumero</string>
|
<string name="medium_phone_number">Telefonnumero</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">sendis bildon.</string>
|
|
||||||
<string name="reply_to_a_video">sendis filmon.</string>
|
|
||||||
<string name="reply_to_an_audio_file">sendis sondosieron.</string>
|
|
||||||
<string name="reply_to_a_file">sendis dosieron.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Invito de %s</string>
|
<string name="room_displayname_invite_from">Invito de %s</string>
|
||||||
<string name="room_displayname_room_invite">Ĉambra invito</string>
|
<string name="room_displayname_room_invite">Ĉambra invito</string>
|
||||||
|
|
||||||
|
|
|
@ -73,13 +73,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s envió una calcomanía.</string>
|
<string name="summary_user_sent_sticker">%1$s envió una calcomanía.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">En respuesta a</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">envió una imagen.</string>
|
|
||||||
<string name="reply_to_a_video">envió un video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">envió un archivo de audio.</string>
|
|
||||||
<string name="reply_to_a_file">envió un archivo.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Invitación de %s</string>
|
<string name="room_displayname_invite_from">Invitación de %s</string>
|
||||||
<string name="room_displayname_room_invite">Invitación de Sala</string>
|
<string name="room_displayname_room_invite">Invitación de Sala</string>
|
||||||
|
|
|
@ -73,13 +73,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s envió una pegatina.</string>
|
<string name="summary_user_sent_sticker">%1$s envió una pegatina.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">En respuesta a</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">envió una imagen.</string>
|
|
||||||
<string name="reply_to_a_video">envió un vídeo.</string>
|
|
||||||
<string name="reply_to_an_audio_file">envió un archivo de audio.</string>
|
|
||||||
<string name="reply_to_a_file">envió un archivo.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Invitación de %s</string>
|
<string name="room_displayname_invite_from">Invitación de %s</string>
|
||||||
<string name="room_displayname_room_invite">Invitación a Sala</string>
|
<string name="room_displayname_room_invite">Invitación a Sala</string>
|
||||||
|
|
|
@ -50,8 +50,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Ei õnnestu dekrüptida: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Ei õnnestu dekrüptida: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Sõnumi saatja seade ei ole selle sõnumi jaoks saatnud dekrüptimisvõtmeid.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Vastuseks kasutajale</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Ei saanud muuta sõnumit</string>
|
<string name="could_not_redact">Ei saanud muuta sõnumit</string>
|
||||||
<string name="unable_to_send_message">Sõnumi saatmine ei õnnestunud</string>
|
<string name="unable_to_send_message">Sõnumi saatmine ei õnnestunud</string>
|
||||||
|
|
||||||
|
@ -67,11 +65,6 @@
|
||||||
<string name="medium_email">E-posti aadress</string>
|
<string name="medium_email">E-posti aadress</string>
|
||||||
<string name="medium_phone_number">Telefoninumber</string>
|
<string name="medium_phone_number">Telefoninumber</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">saatis pildi.</string>
|
|
||||||
<string name="reply_to_a_video">saatis video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">saatis helifaili.</string>
|
|
||||||
<string name="reply_to_a_file">saatis faili.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Kutse kasutajalt %s</string>
|
<string name="room_displayname_invite_from">Kutse kasutajalt %s</string>
|
||||||
<string name="room_displayname_room_invite">Kutse jututuppa</string>
|
<string name="room_displayname_room_invite">Kutse jututuppa</string>
|
||||||
|
|
||||||
|
|
|
@ -63,13 +63,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s erabiltzaileak eranskailu bat bidali du.</string>
|
<string name="summary_user_sent_sticker">%1$s erabiltzaileak eranskailu bat bidali du.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Honi erantzunez</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">irudi bat bidali du.</string>
|
|
||||||
<string name="reply_to_a_video">bideo bat bidali du.</string>
|
|
||||||
<string name="reply_to_an_audio_file">audio fitxategi bat bidali du.</string>
|
|
||||||
<string name="reply_to_a_file">fitxategi bat bidali du.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">%s gelarako gonbidapena</string>
|
<string name="room_displayname_invite_from">%s gelarako gonbidapena</string>
|
||||||
<string name="room_displayname_room_invite">Gela gonbidapena</string>
|
<string name="room_displayname_room_invite">Gela gonbidapena</string>
|
||||||
<string name="room_displayname_two_members">%1$s eta %2$s</string>
|
<string name="room_displayname_two_members">%1$s eta %2$s</string>
|
||||||
|
|
|
@ -51,8 +51,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** ناتوان در رمزگشایی: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** ناتوان در رمزگشایی: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">دستگاه فرستنده، کلیدهای این پیام را برایمان نفرستاده است.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">دستگاه فرستنده، کلیدهای این پیام را برایمان نفرستاده است.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">در پاسخ به</string>
|
|
||||||
|
|
||||||
<string name="unable_to_send_message">ناتوان در فرستادن پیام</string>
|
<string name="unable_to_send_message">ناتوان در فرستادن پیام</string>
|
||||||
|
|
||||||
<string name="message_failed_to_upload">شکست در بارگذاری تصویر</string>
|
<string name="message_failed_to_upload">شکست در بارگذاری تصویر</string>
|
||||||
|
@ -67,11 +65,6 @@
|
||||||
<string name="medium_email">نشانی رایانامه</string>
|
<string name="medium_email">نشانی رایانامه</string>
|
||||||
<string name="medium_phone_number">شماره تلفن</string>
|
<string name="medium_phone_number">شماره تلفن</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">تصویری فرستاد.</string>
|
|
||||||
<string name="reply_to_a_video">ویدیویی فرستاد.</string>
|
|
||||||
<string name="reply_to_an_audio_file">پروندهای صوتی فرستاد.</string>
|
|
||||||
<string name="reply_to_a_file">پروندهای فرستاد.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">دعوت از %s</string>
|
<string name="room_displayname_invite_from">دعوت از %s</string>
|
||||||
<string name="room_displayname_room_invite">دعوت اتاق</string>
|
<string name="room_displayname_room_invite">دعوت اتاق</string>
|
||||||
|
|
||||||
|
|
|
@ -70,13 +70,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s lähetti tarran.</string>
|
<string name="summary_user_sent_sticker">%1$s lähetti tarran.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Vastauksena käyttäjälle</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">oli lähettänyt kuvan.</string>
|
|
||||||
<string name="reply_to_a_video">lähetti videon.</string>
|
|
||||||
<string name="reply_to_an_audio_file">lähetti äänitiedoston.</string>
|
|
||||||
<string name="reply_to_a_file">lähetti tiedoston.</string>
|
|
||||||
|
|
||||||
<plurals name="room_displayname_three_and_more_members">
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
<item quantity="one">%1$s ja yksi muu</item>
|
<item quantity="one">%1$s ja yksi muu</item>
|
||||||
<item quantity="other">%1$s ja %2$d muuta</item>
|
<item quantity="other">%1$s ja %2$d muuta</item>
|
||||||
|
|
|
@ -63,13 +63,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s a envoyé un sticker.</string>
|
<string name="summary_user_sent_sticker">%1$s a envoyé un sticker.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">En réponse à</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">a envoyé une image.</string>
|
|
||||||
<string name="reply_to_a_video">a envoyé une vidéo.</string>
|
|
||||||
<string name="reply_to_an_audio_file">a envoyé un fichier audio.</string>
|
|
||||||
<string name="reply_to_a_file">a envoyé un fichier.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Invitation de %s</string>
|
<string name="room_displayname_invite_from">Invitation de %s</string>
|
||||||
<string name="room_displayname_room_invite">Invitation au salon</string>
|
<string name="room_displayname_room_invite">Invitation au salon</string>
|
||||||
<string name="room_displayname_empty_room">Salon vide</string>
|
<string name="room_displayname_empty_room">Salon vide</string>
|
||||||
|
|
|
@ -50,8 +50,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Imposíbel descifrar: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Imposíbel descifrar: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">O dispositivo do que envía non enviou as chaves desta mensaxe.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">O dispositivo do que envía non enviou as chaves desta mensaxe.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Respondéndolle a</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Non se puido redactar</string>
|
<string name="could_not_redact">Non se puido redactar</string>
|
||||||
<string name="unable_to_send_message">Non foi posíbel enviar a mensaxe</string>
|
<string name="unable_to_send_message">Non foi posíbel enviar a mensaxe</string>
|
||||||
|
|
||||||
|
@ -64,11 +62,6 @@
|
||||||
|
|
||||||
<string name="medium_phone_number">Número de teléfono</string>
|
<string name="medium_phone_number">Número de teléfono</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">Responder a</string>
|
|
||||||
<string name="reply_to_a_video">enviar un vídeo.</string>
|
|
||||||
<string name="reply_to_an_audio_file">enviar un ficheiro de son.</string>
|
|
||||||
<string name="reply_to_a_file">enviar un ficheiro.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_two_members">%1$s e %2$s</string>
|
<string name="room_displayname_two_members">%1$s e %2$s</string>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -62,13 +62,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s küldött egy matricát.</string>
|
<string name="summary_user_sent_sticker">%1$s küldött egy matricát.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Válasz erre:</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">képet küldött.</string>
|
|
||||||
<string name="reply_to_a_video">videót küldött.</string>
|
|
||||||
<string name="reply_to_an_audio_file">hangfájlt küldött.</string>
|
|
||||||
<string name="reply_to_a_file">fájlt küldött.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Meghívó tőle: %s</string>
|
<string name="room_displayname_invite_from">Meghívó tőle: %s</string>
|
||||||
<string name="room_displayname_room_invite">Meghívó egy szobába</string>
|
<string name="room_displayname_room_invite">Meghívó egy szobába</string>
|
||||||
<string name="room_displayname_two_members">%1$s és %2$s</string>
|
<string name="room_displayname_two_members">%1$s és %2$s</string>
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
|
|
||||||
<string name="notice_avatar_changed_too">(einnig var skipt um auðkennismynd)</string>
|
<string name="notice_avatar_changed_too">(einnig var skipt um auðkennismynd)</string>
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Mistókst að afkóða: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Mistókst að afkóða: %s **</string>
|
||||||
<string name="message_reply_to_prefix">Sem svar til</string>
|
|
||||||
|
|
||||||
<string name="unable_to_send_message">Gat ekki sent skilaboð</string>
|
<string name="unable_to_send_message">Gat ekki sent skilaboð</string>
|
||||||
|
|
||||||
|
|
|
@ -62,13 +62,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s ha inviato un adesivo.</string>
|
<string name="summary_user_sent_sticker">%1$s ha inviato un adesivo.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">In risposta a</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">inviata un\'immagine.</string>
|
|
||||||
<string name="reply_to_a_video">inviato un video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">inviato un file audio.</string>
|
|
||||||
<string name="reply_to_a_file">inviato un file.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Invito da %s</string>
|
<string name="room_displayname_invite_from">Invito da %s</string>
|
||||||
<string name="room_displayname_room_invite">Invito nella stanza</string>
|
<string name="room_displayname_room_invite">Invito nella stanza</string>
|
||||||
|
|
|
@ -56,8 +56,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** 解読できません: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** 解読できません: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">送信者の端末からこのメッセージのキーが送信されていません。</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">送信者の端末からこのメッセージのキーが送信されていません。</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">に返信</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">修正できませんでした</string>
|
<string name="could_not_redact">修正できませんでした</string>
|
||||||
<string name="unable_to_send_message">メッセージを送信できません</string>
|
<string name="unable_to_send_message">メッセージを送信できません</string>
|
||||||
|
|
||||||
|
@ -73,9 +71,4 @@
|
||||||
<string name="medium_email">メールアドレス</string>
|
<string name="medium_email">メールアドレス</string>
|
||||||
<string name="medium_phone_number">電話番号</string>
|
<string name="medium_phone_number">電話番号</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">画像を送信しました。</string>
|
|
||||||
<string name="reply_to_a_video">動画を送りました。</string>
|
|
||||||
<string name="reply_to_an_audio_file">音声ファイルを送信しました。</string>
|
|
||||||
<string name="reply_to_a_file">ファイルを送信しました。</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -52,8 +52,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** 암호를 복호화할 수 없음: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** 암호를 복호화할 수 없음: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">발신인의 기기에서 이 메시지의 키를 보내지 않았습니다.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">발신인의 기기에서 이 메시지의 키를 보내지 않았습니다.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">관련 대화</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">검열할 수 없습니다</string>
|
<string name="could_not_redact">검열할 수 없습니다</string>
|
||||||
<string name="unable_to_send_message">메시지를 보낼 수 없습니다</string>
|
<string name="unable_to_send_message">메시지를 보낼 수 없습니다</string>
|
||||||
|
|
||||||
|
@ -69,11 +67,6 @@
|
||||||
<string name="medium_email">이메일 주소</string>
|
<string name="medium_email">이메일 주소</string>
|
||||||
<string name="medium_phone_number">전화번호</string>
|
<string name="medium_phone_number">전화번호</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">사진을 보냈습니다.</string>
|
|
||||||
<string name="reply_to_a_video">동영상을 보냈습니다.</string>
|
|
||||||
<string name="reply_to_an_audio_file">오디오 파일을 보냈습니다.</string>
|
|
||||||
<string name="reply_to_a_file">파일을 보냈습니다.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">%s에서 초대함</string>
|
<string name="room_displayname_invite_from">%s에서 초대함</string>
|
||||||
<string name="room_displayname_room_invite">방 초대</string>
|
<string name="room_displayname_room_invite">방 초대</string>
|
||||||
|
|
||||||
|
|
|
@ -71,13 +71,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s heeft een sticker gestuurd.</string>
|
<string name="summary_user_sent_sticker">%1$s heeft een sticker gestuurd.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Als antwoord op</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">heeft een afbeelding gestuurd.</string>
|
|
||||||
<string name="reply_to_a_video">heeft een video gestuurd.</string>
|
|
||||||
<string name="reply_to_an_audio_file">heeft een audiobestand gestuurd.</string>
|
|
||||||
<string name="reply_to_a_file">heeft een bestand gestuurd.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Uitnodiging van %s</string>
|
<string name="room_displayname_invite_from">Uitnodiging van %s</string>
|
||||||
<string name="room_displayname_room_invite">Gespreksuitnodiging</string>
|
<string name="room_displayname_room_invite">Gespreksuitnodiging</string>
|
||||||
|
|
|
@ -49,8 +49,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Fekk ikkje til å dekryptera: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Fekk ikkje til å dekryptera: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Avsendareiningi hev ikkje sendt oss nyklane fyr denna meldingi.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Som svar til</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Kunde ikkje gjera um</string>
|
<string name="could_not_redact">Kunde ikkje gjera um</string>
|
||||||
<string name="unable_to_send_message">Fekk ikkje å senda meldingi</string>
|
<string name="unable_to_send_message">Fekk ikkje å senda meldingi</string>
|
||||||
|
|
||||||
|
@ -64,11 +62,6 @@
|
||||||
<string name="medium_email">Epostadresse</string>
|
<string name="medium_email">Epostadresse</string>
|
||||||
<string name="medium_phone_number">Telefonnummer</string>
|
<string name="medium_phone_number">Telefonnummer</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">sende eit bilæte.</string>
|
|
||||||
<string name="reply_to_a_video">sende ein video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">sende ei ljodfil.</string>
|
|
||||||
<string name="reply_to_a_file">sende ei fil.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Innbjoding frå %s</string>
|
<string name="room_displayname_invite_from">Innbjoding frå %s</string>
|
||||||
<string name="room_displayname_room_invite">Rominnbjoding</string>
|
<string name="room_displayname_room_invite">Rominnbjoding</string>
|
||||||
<string name="room_displayname_two_members">%1$s og %2$s</string>
|
<string name="room_displayname_two_members">%1$s og %2$s</string>
|
||||||
|
|
|
@ -42,7 +42,6 @@
|
||||||
<string name="notice_room_withdraw">%1$s wycofał(a) zaproszenie %2$s</string>
|
<string name="notice_room_withdraw">%1$s wycofał(a) zaproszenie %2$s</string>
|
||||||
<string name="notice_answered_call">%s odebrał(a) połączenie.</string>
|
<string name="notice_answered_call">%s odebrał(a) połączenie.</string>
|
||||||
<string name="notice_avatar_changed_too">(awatar też został zmieniony)</string>
|
<string name="notice_avatar_changed_too">(awatar też został zmieniony)</string>
|
||||||
<string name="message_reply_to_prefix">W odpowiedzi do</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Zaproszenie od %s</string>
|
<string name="room_displayname_invite_from">Zaproszenie od %s</string>
|
||||||
<string name="room_displayname_room_invite">Zaproszenie do pokoju</string>
|
<string name="room_displayname_room_invite">Zaproszenie do pokoju</string>
|
||||||
|
@ -76,11 +75,6 @@
|
||||||
<string name="could_not_redact">Nie można zredagować</string>
|
<string name="could_not_redact">Nie można zredagować</string>
|
||||||
<string name="room_error_join_failed_empty_room">Obecnie nie jest możliwe ponowne dołączenie do pustego pokoju.</string>
|
<string name="room_error_join_failed_empty_room">Obecnie nie jest możliwe ponowne dołączenie do pustego pokoju.</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">wyślij zdjęcie.</string>
|
|
||||||
<string name="reply_to_a_video">wyślij wideo.</string>
|
|
||||||
<string name="reply_to_an_audio_file">wyślij plik audio.</string>
|
|
||||||
<string name="reply_to_a_file">wyślij plik.</string>
|
|
||||||
|
|
||||||
<string name="notice_event_redacted">Wiadomość usunięta</string>
|
<string name="notice_event_redacted">Wiadomość usunięta</string>
|
||||||
<string name="notice_event_redacted_by">Wiadomość usunięta przez %1$s</string>
|
<string name="notice_event_redacted_by">Wiadomość usunięta przez %1$s</string>
|
||||||
<string name="notice_event_redacted_with_reason">Wiadomość usunięta [powód: %1$s]</string>
|
<string name="notice_event_redacted_with_reason">Wiadomość usunięta [powód: %1$s]</string>
|
||||||
|
|
|
@ -74,13 +74,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s enviou um sticker.</string>
|
<string name="summary_user_sent_sticker">%1$s enviou um sticker.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Em resposta a</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">enviou uma imagem.</string>
|
|
||||||
<string name="reply_to_a_video">enviou um vídeo.</string>
|
|
||||||
<string name="reply_to_an_audio_file">enviou um arquivo de áudio.</string>
|
|
||||||
<string name="reply_to_a_file">enviou um arquivo.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Convite de %s</string>
|
<string name="room_displayname_invite_from">Convite de %s</string>
|
||||||
<string name="room_displayname_room_invite">Convite para sala</string>
|
<string name="room_displayname_room_invite">Convite para sala</string>
|
||||||
|
|
|
@ -73,13 +73,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s отправил стикер.</string>
|
<string name="summary_user_sent_sticker">%1$s отправил стикер.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">В ответ на</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">отправил изображение.</string>
|
|
||||||
<string name="reply_to_a_video">отправил видео.</string>
|
|
||||||
<string name="reply_to_an_audio_file">отправил аудиофайл.</string>
|
|
||||||
<string name="reply_to_a_file">отправил файл.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Приглашение от %s</string>
|
<string name="room_displayname_invite_from">Приглашение от %s</string>
|
||||||
<string name="room_displayname_room_invite">Приглашение в комнату</string>
|
<string name="room_displayname_room_invite">Приглашение в комнату</string>
|
||||||
|
|
|
@ -62,13 +62,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s poslal nálepku.</string>
|
<string name="summary_user_sent_sticker">%1$s poslal nálepku.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Odpoveď na</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">odoslal obrázok.</string>
|
|
||||||
<string name="reply_to_a_video">odoslal video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">odoslal zvukový súbor.</string>
|
|
||||||
<string name="reply_to_a_file">Odoslal súbor.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Pozvanie od %s</string>
|
<string name="room_displayname_invite_from">Pozvanie od %s</string>
|
||||||
<string name="room_displayname_room_invite">Pozvanie do miestnosti</string>
|
<string name="room_displayname_room_invite">Pozvanie do miestnosti</string>
|
||||||
<string name="room_displayname_two_members">%1$s a %2$s</string>
|
<string name="room_displayname_two_members">%1$s a %2$s</string>
|
||||||
|
|
|
@ -34,8 +34,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** S’arrihet të shfshehtëzohet: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** S’arrihet të shfshehtëzohet: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Pajisja e dërguesit nuk na ka dërguar kyçet për këtë mesazh.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Pajisja e dërguesit nuk na ka dërguar kyçet për këtë mesazh.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Në përgjigje të</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">S’u redaktua dot</string>
|
<string name="could_not_redact">S’u redaktua dot</string>
|
||||||
<string name="unable_to_send_message">S’arrihet të dërgohet mesazh</string>
|
<string name="unable_to_send_message">S’arrihet të dërgohet mesazh</string>
|
||||||
|
|
||||||
|
@ -51,11 +49,6 @@
|
||||||
<string name="medium_email">Adresë email</string>
|
<string name="medium_email">Adresë email</string>
|
||||||
<string name="medium_phone_number">Numër telefoni</string>
|
<string name="medium_phone_number">Numër telefoni</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">dërgoi një figurë.</string>
|
|
||||||
<string name="reply_to_a_video">dërgoi një video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">dërgoi një kartelë audio.</string>
|
|
||||||
<string name="reply_to_a_file">dërgoi një kartelë.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Ftesë nga %s</string>
|
<string name="room_displayname_invite_from">Ftesë nga %s</string>
|
||||||
<string name="room_displayname_room_invite">Ftesë Dhome</string>
|
<string name="room_displayname_room_invite">Ftesë Dhome</string>
|
||||||
|
|
||||||
|
|
|
@ -56,8 +56,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Неможливо розшифрувати: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Неможливо розшифрувати: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">Пристрій відправника не надіслав нам ключ для цього повідомлення.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">Пристрій відправника не надіслав нам ключ для цього повідомлення.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">У відповідь на</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Неможливо відредагувати</string>
|
<string name="could_not_redact">Неможливо відредагувати</string>
|
||||||
<string name="unable_to_send_message">Не вдалося надіслати повідомлення</string>
|
<string name="unable_to_send_message">Не вдалося надіслати повідомлення</string>
|
||||||
|
|
||||||
|
@ -71,11 +69,6 @@
|
||||||
<string name="medium_email">Адреса електронної пошти</string>
|
<string name="medium_email">Адреса електронної пошти</string>
|
||||||
<string name="medium_phone_number">Номер телефону</string>
|
<string name="medium_phone_number">Номер телефону</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">надіслав зображення.</string>
|
|
||||||
<string name="reply_to_a_video">надіслав відео.</string>
|
|
||||||
<string name="reply_to_an_audio_file">надіслав аудіо файл.</string>
|
|
||||||
<string name="reply_to_a_file">надіслав файл.</string>
|
|
||||||
|
|
||||||
<plurals name="room_displayname_three_and_more_members">
|
<plurals name="room_displayname_three_and_more_members">
|
||||||
<item quantity="one">%1$s та 1 інший</item>
|
<item quantity="one">%1$s та 1 інший</item>
|
||||||
<item quantity="few">%1$s та %2$d інші</item>
|
<item quantity="few">%1$s та %2$d інші</item>
|
||||||
|
|
|
@ -50,8 +50,6 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Kun nie ountsleuteln: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Kun nie ountsleuteln: %s **</string>
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">’t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">’t Toestel van den afzender èt geen sleutels vo da bericht hier gesteurd.</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">Als antwoord ip</string>
|
|
||||||
|
|
||||||
<string name="could_not_redact">Kosteg nie verwyderd wordn</string>
|
<string name="could_not_redact">Kosteg nie verwyderd wordn</string>
|
||||||
<string name="unable_to_send_message">Kosteg ’t bericht nie verzendn</string>
|
<string name="unable_to_send_message">Kosteg ’t bericht nie verzendn</string>
|
||||||
|
|
||||||
|
@ -67,11 +65,6 @@
|
||||||
<string name="medium_email">E-mailadresse</string>
|
<string name="medium_email">E-mailadresse</string>
|
||||||
<string name="medium_phone_number">Telefongnumero</string>
|
<string name="medium_phone_number">Telefongnumero</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">èt e fotootje gesteurd.</string>
|
|
||||||
<string name="reply_to_a_video">èt e filmtje gesteurd.</string>
|
|
||||||
<string name="reply_to_an_audio_file">èt e geluudsfragment gesteurd.</string>
|
|
||||||
<string name="reply_to_a_file">èt e bestand gesteurd.</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">Uutnodigienge van %s</string>
|
<string name="room_displayname_invite_from">Uutnodigienge van %s</string>
|
||||||
<string name="room_displayname_room_invite">Gespreksuutnodigienge</string>
|
<string name="room_displayname_room_invite">Gespreksuutnodigienge</string>
|
||||||
|
|
||||||
|
|
|
@ -63,13 +63,6 @@
|
||||||
<string name="summary_message">%1$s:%2$s</string>
|
<string name="summary_message">%1$s:%2$s</string>
|
||||||
<string name="summary_user_sent_sticker">%1$s 发送了一张贴纸。</string>
|
<string name="summary_user_sent_sticker">%1$s 发送了一张贴纸。</string>
|
||||||
|
|
||||||
<string name="reply_to_an_image">发送了一张图片。</string>
|
|
||||||
<string name="reply_to_a_video">发送了一个视频。</string>
|
|
||||||
<string name="reply_to_an_audio_file">发送了一段音频。</string>
|
|
||||||
<string name="reply_to_a_file">发送了一个文件。</string>
|
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">回复</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_empty_room">空聊天室</string>
|
<string name="room_displayname_empty_room">空聊天室</string>
|
||||||
<string name="room_displayname_invite_from">来自 %s 的邀请</string>
|
<string name="room_displayname_invite_from">来自 %s 的邀请</string>
|
||||||
<string name="room_displayname_room_invite">聊天室邀请</string>
|
<string name="room_displayname_room_invite">聊天室邀请</string>
|
||||||
|
|
|
@ -62,13 +62,6 @@
|
||||||
|
|
||||||
<string name="summary_user_sent_sticker">%1$s 傳送了一張貼圖。</string>
|
<string name="summary_user_sent_sticker">%1$s 傳送了一張貼圖。</string>
|
||||||
|
|
||||||
<string name="message_reply_to_prefix">回覆</string>
|
|
||||||
|
|
||||||
<string name="reply_to_an_image">傳送了圖片。</string>
|
|
||||||
<string name="reply_to_a_video">傳送了影片。</string>
|
|
||||||
<string name="reply_to_an_audio_file">傳送了音訊檔案。</string>
|
|
||||||
<string name="reply_to_a_file">傳送了檔案。</string>
|
|
||||||
|
|
||||||
<string name="room_displayname_invite_from">來自%s 的邀請</string>
|
<string name="room_displayname_invite_from">來自%s 的邀請</string>
|
||||||
<string name="room_displayname_room_invite">聊天室邀請</string>
|
<string name="room_displayname_room_invite">聊天室邀請</string>
|
||||||
<string name="room_displayname_two_members">%1$s 和 %2$s</string>
|
<string name="room_displayname_two_members">%1$s 和 %2$s</string>
|
||||||
|
|
|
@ -112,7 +112,6 @@
|
||||||
<string name="notice_crypto_error_unkwown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
<string name="notice_crypto_error_unkwown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<string name="message_reply_to_prefix">In reply to</string>
|
|
||||||
|
|
||||||
<!-- Room Screen -->
|
<!-- Room Screen -->
|
||||||
<string name="could_not_redact">Could not redact</string>
|
<string name="could_not_redact">Could not redact</string>
|
||||||
|
@ -139,12 +138,6 @@
|
||||||
<string name="medium_email">Email address</string>
|
<string name="medium_email">Email address</string>
|
||||||
<string name="medium_phone_number">Phone number</string>
|
<string name="medium_phone_number">Phone number</string>
|
||||||
|
|
||||||
<!-- Reply to -->
|
|
||||||
<string name="reply_to_an_image">sent an image.</string>
|
|
||||||
<string name="reply_to_a_video">sent a video.</string>
|
|
||||||
<string name="reply_to_an_audio_file">sent an audio file.</string>
|
|
||||||
<string name="reply_to_a_file">sent a file.</string>
|
|
||||||
|
|
||||||
<!-- Room display name -->
|
<!-- Room display name -->
|
||||||
<string name="room_displayname_invite_from">Invite from %s</string>
|
<string name="room_displayname_invite_from">Invite from %s</string>
|
||||||
<string name="room_displayname_room_invite">Room Invite</string>
|
<string name="room_displayname_room_invite">Room Invite</string>
|
||||||
|
|
|
@ -17,7 +17,7 @@ androidExtensions {
|
||||||
// Note: 2 digits max for each value
|
// Note: 2 digits max for each value
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 91
|
ext.versionMinor = 91
|
||||||
ext.versionPatch = 3
|
ext.versionPatch = 4
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
@ -106,6 +106,11 @@ def buildNumber = System.env.BUILDKITE_BUILD_NUMBER as Integer ?: 0
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
|
|
||||||
|
// Due to a bug introduced in Android gradle plugin 3.6.0, we have to specify the ndk version to use
|
||||||
|
// Ref: https://issuetracker.google.com/issues/144111441
|
||||||
|
ndkVersion "21.3.6528147"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "im.vector.app"
|
applicationId "im.vector.app"
|
||||||
// Set to API 21: see #405
|
// Set to API 21: see #405
|
||||||
|
@ -232,8 +237,7 @@ android {
|
||||||
lintOptions {
|
lintOptions {
|
||||||
lintConfig file("lint.xml")
|
lintConfig file("lint.xml")
|
||||||
|
|
||||||
// TODO Restore true once pb with WorkManager is fixed
|
abortOnError true
|
||||||
abortOnError false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
@ -21,3 +21,6 @@
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-keep class im.vector.riotx.features.** { *; }
|
-keep class im.vector.riotx.features.** { *; }
|
||||||
|
|
||||||
|
## print all the rules in a file
|
||||||
|
# -printconfiguration ../proguard_files/full-r8-config.txt
|
||||||
|
|
|
@ -247,6 +247,12 @@
|
||||||
|
|
||||||
<!-- Providers -->
|
<!-- Providers -->
|
||||||
|
|
||||||
|
<!-- Remove WorkManagerInitializer Provider because we are using on-demand initialization of WorkManager-->
|
||||||
|
<provider
|
||||||
|
android:name="androidx.work.impl.WorkManagerInitializer"
|
||||||
|
android:authorities="${applicationId}.workmanager-init"
|
||||||
|
tools:node="remove" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileProvider"
|
android:authorities="${applicationId}.fileProvider"
|
||||||
|
|
|
@ -63,7 +63,13 @@ import java.util.Locale
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.Provider, androidx.work.Configuration.Provider {
|
import androidx.work.Configuration as WorkConfiguration
|
||||||
|
|
||||||
|
class VectorApplication :
|
||||||
|
Application(),
|
||||||
|
HasVectorInjector,
|
||||||
|
MatrixConfiguration.Provider,
|
||||||
|
WorkConfiguration.Provider {
|
||||||
|
|
||||||
lateinit var appContext: Context
|
lateinit var appContext: Context
|
||||||
@Inject lateinit var legacySessionImporter: LegacySessionImporter
|
@Inject lateinit var legacySessionImporter: LegacySessionImporter
|
||||||
|
@ -85,6 +91,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
@Inject lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager
|
@Inject lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager
|
||||||
|
|
||||||
lateinit var vectorComponent: VectorComponent
|
lateinit var vectorComponent: VectorComponent
|
||||||
|
|
||||||
// font thread handler
|
// font thread handler
|
||||||
private var fontThreadHandler: Handler? = null
|
private var fontThreadHandler: Handler? = null
|
||||||
|
|
||||||
|
@ -157,7 +164,11 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
|
|
||||||
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
||||||
|
|
||||||
override fun getWorkManagerConfiguration() = androidx.work.Configuration.Builder().setExecutor(Executors.newCachedThreadPool()).build()
|
override fun getWorkManagerConfiguration(): WorkConfiguration {
|
||||||
|
return WorkConfiguration.Builder()
|
||||||
|
.setExecutor(Executors.newCachedThreadPool())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
override fun injector(): VectorComponent {
|
override fun injector(): VectorComponent {
|
||||||
return vectorComponent
|
return vectorComponent
|
||||||
|
|
|
@ -17,29 +17,39 @@
|
||||||
package im.vector.riotx.core.utils
|
package im.vector.riotx.core.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.DownloadManager
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.provider.Browser
|
import android.provider.Browser
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.browser.customtabs.CustomTabsSession
|
import androidx.browser.customtabs.CustomTabsSession
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
import im.vector.riotx.BuildConfig
|
import im.vector.riotx.BuildConfig
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.features.notifications.NotificationUtils
|
import im.vector.riotx.features.notifications.NotificationUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import okio.source
|
import okio.source
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -301,42 +311,20 @@ fun shareMedia(context: Context, file: File, mediaMimeType: String?) {
|
||||||
|
|
||||||
fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String?, notificationUtils: NotificationUtils) {
|
fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String?, notificationUtils: NotificationUtils) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val externalContentUri: Uri
|
val values = ContentValues().apply {
|
||||||
val values = ContentValues()
|
put(MediaStore.Images.Media.TITLE, title)
|
||||||
when {
|
put(MediaStore.Images.Media.DISPLAY_NAME, title)
|
||||||
mediaMimeType?.startsWith("image/") == true -> {
|
put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType)
|
||||||
externalContentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
|
||||||
values.put(MediaStore.Images.Media.TITLE, title)
|
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
|
||||||
values.put(MediaStore.Images.Media.DISPLAY_NAME, title)
|
|
||||||
values.put(MediaStore.Images.Media.MIME_TYPE, mediaMimeType)
|
|
||||||
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
|
|
||||||
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
mediaMimeType?.startsWith("video/") == true -> {
|
|
||||||
externalContentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
|
||||||
values.put(MediaStore.Video.Media.TITLE, title)
|
|
||||||
values.put(MediaStore.Video.Media.DISPLAY_NAME, title)
|
|
||||||
values.put(MediaStore.Video.Media.MIME_TYPE, mediaMimeType)
|
|
||||||
values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis())
|
|
||||||
values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
mediaMimeType?.startsWith("audio/") == true -> {
|
|
||||||
externalContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
|
||||||
values.put(MediaStore.Audio.Media.TITLE, title)
|
|
||||||
values.put(MediaStore.Audio.Media.DISPLAY_NAME, title)
|
|
||||||
values.put(MediaStore.Audio.Media.MIME_TYPE, mediaMimeType)
|
|
||||||
values.put(MediaStore.Audio.Media.DATE_ADDED, System.currentTimeMillis())
|
|
||||||
values.put(MediaStore.Audio.Media.DATE_TAKEN, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
externalContentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
|
||||||
values.put(MediaStore.Downloads.TITLE, title)
|
|
||||||
values.put(MediaStore.Downloads.DISPLAY_NAME, title)
|
|
||||||
values.put(MediaStore.Downloads.MIME_TYPE, mediaMimeType)
|
|
||||||
values.put(MediaStore.Downloads.DATE_ADDED, System.currentTimeMillis())
|
|
||||||
values.put(MediaStore.Downloads.DATE_TAKEN, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val externalContentUri = when {
|
||||||
|
mediaMimeType?.startsWith("image/") == true -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
|
mediaMimeType?.startsWith("video/") == true -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
|
mediaMimeType?.startsWith("audio/") == true -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||||
|
else -> MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
||||||
|
}
|
||||||
|
|
||||||
val uri = context.contentResolver.insert(externalContentUri, values)
|
val uri = context.contentResolver.insert(externalContentUri, values)
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Toast.makeText(context, R.string.error_saving_media_file, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, R.string.error_saving_media_file, Toast.LENGTH_LONG).show()
|
||||||
|
@ -357,16 +345,70 @@ fun saveMedia(context: Context, file: File, title: String, mediaMimeType: String
|
||||||
notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification)
|
notificationUtils.showNotificationMessage("DL", uri.hashCode(), notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO add notification?
|
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
saveMediaLegacy(context, mediaMimeType, title, file)
|
||||||
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
|
}
|
||||||
mediaScanIntent.data = Uri.fromFile(file)
|
}
|
||||||
context.sendBroadcast(mediaScanIntent)
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun saveMediaLegacy(context: Context, mediaMimeType: String?, title: String, file: File) {
|
||||||
|
val state = Environment.getExternalStorageState()
|
||||||
|
if (Environment.MEDIA_MOUNTED != state) {
|
||||||
|
context.toast(context.getString(R.string.error_saving_media_file))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val dest = when {
|
||||||
|
mediaMimeType?.startsWith("image/") == true -> Environment.DIRECTORY_PICTURES
|
||||||
|
mediaMimeType?.startsWith("video/") == true -> Environment.DIRECTORY_MOVIES
|
||||||
|
mediaMimeType?.startsWith("audio/") == true -> Environment.DIRECTORY_MUSIC
|
||||||
|
else -> Environment.DIRECTORY_DOWNLOADS
|
||||||
|
}
|
||||||
|
val downloadDir = Environment.getExternalStoragePublicDirectory(dest)
|
||||||
|
try {
|
||||||
|
val outputFilename = if (title.substringAfterLast('.', "").isEmpty()) {
|
||||||
|
val extension = mediaMimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) }
|
||||||
|
"$title.$extension"
|
||||||
|
} else {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
val savedFile = saveFileIntoLegacy(file, downloadDir, outputFilename)
|
||||||
|
if (savedFile != null) {
|
||||||
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager
|
||||||
|
downloadManager?.addCompletedDownload(
|
||||||
|
savedFile.name,
|
||||||
|
title,
|
||||||
|
true,
|
||||||
|
mediaMimeType ?: "application/octet-stream",
|
||||||
|
savedFile.absolutePath,
|
||||||
|
savedFile.length(),
|
||||||
|
true)
|
||||||
|
addToGallery(savedFile, mediaMimeType, context)
|
||||||
|
}
|
||||||
|
} catch (error: Throwable) {
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
context.toast(context.getString(R.string.error_saving_media_file))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addToGallery(savedFile: File, mediaMimeType: String?, context: Context) {
|
||||||
|
// MediaScannerConnection provides a way for applications to pass a newly created or downloaded media file to the media scanner service.
|
||||||
|
var mediaConnection: MediaScannerConnection? = null
|
||||||
|
val mediaScannerConnectionClient: MediaScannerConnection.MediaScannerConnectionClient = object : MediaScannerConnection.MediaScannerConnectionClient {
|
||||||
|
override fun onMediaScannerConnected() {
|
||||||
|
mediaConnection?.scanFile(savedFile.path, mediaMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScanCompleted(path: String, uri: Uri?) {
|
||||||
|
if (path == savedFile.path) mediaConnection?.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediaConnection = MediaScannerConnection(context, mediaScannerConnectionClient).apply { connect() }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the play store to the provided application Id, default to this app
|
* Open the play store to the provided application Id, default to this app
|
||||||
*/
|
*/
|
||||||
|
@ -381,3 +423,76 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==============================================================================================================
|
||||||
|
// Media utils
|
||||||
|
// ==============================================================================================================
|
||||||
|
/**
|
||||||
|
* Copy a file into a dstPath directory.
|
||||||
|
* The output filename can be provided.
|
||||||
|
* The output file is not overridden if it is already exist.
|
||||||
|
*
|
||||||
|
* ~~ This is copied from the old matrix sdk ~~
|
||||||
|
*
|
||||||
|
* @param sourceFile the file source path
|
||||||
|
* @param dstDirPath the dst path
|
||||||
|
* @param outputFilename optional the output filename
|
||||||
|
* @param callback the asynchronous callback
|
||||||
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: String?): File? {
|
||||||
|
// defines another name for the external media
|
||||||
|
val dstFileName: String
|
||||||
|
|
||||||
|
// build a filename is not provided
|
||||||
|
if (null == outputFilename) {
|
||||||
|
// extract the file extension from the uri
|
||||||
|
val dotPos = sourceFile.name.lastIndexOf(".")
|
||||||
|
var fileExt = ""
|
||||||
|
if (dotPos > 0) {
|
||||||
|
fileExt = sourceFile.name.substring(dotPos)
|
||||||
|
}
|
||||||
|
dstFileName = "vector_" + System.currentTimeMillis() + fileExt
|
||||||
|
} else {
|
||||||
|
dstFileName = outputFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
var dstFile = File(dstDirPath, dstFileName)
|
||||||
|
|
||||||
|
// if the file already exists, append a marker
|
||||||
|
if (dstFile.exists()) {
|
||||||
|
var baseFileName = dstFileName
|
||||||
|
var fileExt = ""
|
||||||
|
val lastDotPos = dstFileName.lastIndexOf(".")
|
||||||
|
if (lastDotPos > 0) {
|
||||||
|
baseFileName = dstFileName.substring(0, lastDotPos)
|
||||||
|
fileExt = dstFileName.substring(lastDotPos)
|
||||||
|
}
|
||||||
|
var counter = 1
|
||||||
|
while (dstFile.exists()) {
|
||||||
|
dstFile = File(dstDirPath, "$baseFileName($counter)$fileExt")
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy source file to destination
|
||||||
|
var inputStream: FileInputStream? = null
|
||||||
|
var outputStream: FileOutputStream? = null
|
||||||
|
try {
|
||||||
|
dstFile.createNewFile()
|
||||||
|
inputStream = FileInputStream(sourceFile)
|
||||||
|
outputStream = FileOutputStream(dstFile)
|
||||||
|
val buffer = ByteArray(1024 * 10)
|
||||||
|
var len: Int
|
||||||
|
while (inputStream.read(buffer).also { len = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, len)
|
||||||
|
}
|
||||||
|
return dstFile
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
// Close resources
|
||||||
|
tryThis { inputStream?.close() }
|
||||||
|
tryThis { outputStream?.close() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -37,3 +37,11 @@ internal fun String.ensureProtocol(): String {
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun String.ensureTrailingSlash(): String {
|
||||||
|
return when {
|
||||||
|
isEmpty() -> this
|
||||||
|
!endsWith("/") -> "$this/"
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ abstract class FormEditTextWithButtonItem : VectorEpoxyModel<FormEditTextWithBut
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
holder.textInputLayout.isEnabled = enabled
|
holder.textInputLayout.isEnabled = enabled
|
||||||
holder.textInputLayout.hint = hint
|
holder.textInputLayout.hint = hint
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
|
@ -222,6 +223,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
|
|
||||||
private const val AUDIO_CALL_PERMISSION_REQUEST_CODE = 1
|
private const val AUDIO_CALL_PERMISSION_REQUEST_CODE = 1
|
||||||
private const val VIDEO_CALL_PERMISSION_REQUEST_CODE = 2
|
private const val VIDEO_CALL_PERMISSION_REQUEST_CODE = 2
|
||||||
|
private const val SAVE_ATTACHEMENT_REQUEST_CODE = 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize the display name.
|
* Sanitize the display name.
|
||||||
|
@ -1194,17 +1196,12 @@ class RoomDetailFragment @Inject constructor(
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
// PERMISSION_REQUEST_CODE_DOWNLOAD_FILE -> {
|
SAVE_ATTACHEMENT_REQUEST_CODE -> {
|
||||||
// val action = roomDetailViewModel.pendingAction
|
sharedActionViewModel.pendingAction?.let {
|
||||||
// if (action != null) {
|
handleActions(it)
|
||||||
// (action as? RoomDetailAction.DownloadFile)
|
sharedActionViewModel.pendingAction = null
|
||||||
// ?.messageFileContent
|
}
|
||||||
// ?.getFileName()
|
}
|
||||||
// ?.let { showSnackWithMessage(getString(R.string.downloading_file, it)) }
|
|
||||||
// roomDetailViewModel.pendingAction = null
|
|
||||||
// roomDetailViewModel.handle(action)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
PERMISSION_REQUEST_CODE_INCOMING_URI -> {
|
PERMISSION_REQUEST_CODE_INCOMING_URI -> {
|
||||||
val pendingUri = roomDetailViewModel.pendingUri
|
val pendingUri = roomDetailViewModel.pendingUri
|
||||||
if (pendingUri != null) {
|
if (pendingUri != null) {
|
||||||
|
@ -1357,6 +1354,11 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSaveActionClicked(action: EventSharedAction.Save) {
|
private fun onSaveActionClicked(action: EventSharedAction.Save) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
|
||||||
|
&& !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, SAVE_ATTACHEMENT_REQUEST_CODE)) {
|
||||||
|
sharedActionViewModel.pendingAction = action
|
||||||
|
return
|
||||||
|
}
|
||||||
session.fileService().downloadFile(
|
session.fileService().downloadFile(
|
||||||
downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
|
downloadMode = FileService.DownloadMode.FOR_EXTERNAL_SHARE,
|
||||||
id = action.eventId,
|
id = action.eventId,
|
||||||
|
|
|
@ -21,4 +21,6 @@ import javax.inject.Inject
|
||||||
/**
|
/**
|
||||||
* Activity shared view model to handle message actions
|
* Activity shared view model to handle message actions
|
||||||
*/
|
*/
|
||||||
class MessageSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<EventSharedAction>()
|
class MessageSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<EventSharedAction>() {
|
||||||
|
var pendingAction : EventSharedAction? = null
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
||||||
|
|
||||||
override fun showFailure(throwable: Throwable) {
|
override fun showFailure(throwable: Throwable) {
|
||||||
when (throwable) {
|
when (throwable) {
|
||||||
|
is Failure.Cancelled ->
|
||||||
|
/* Ignore this error, user has cancelled the action */
|
||||||
|
Unit
|
||||||
is Failure.ServerError ->
|
is Failure.ServerError ->
|
||||||
if (throwable.error.code == MatrixError.M_FORBIDDEN
|
if (throwable.error.code == MatrixError.M_FORBIDDEN
|
||||||
&& throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) {
|
&& throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) {
|
||||||
|
|
|
@ -151,8 +151,8 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
// TODO Disabled because it provokes a flickering
|
// TODO Disabled because it provokes a flickering
|
||||||
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
// ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
|
||||||
})
|
})
|
||||||
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone()
|
is LoginViewEvents.OnServerSelectionDone -> onServerSelectionDone(loginViewEvents)
|
||||||
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected()
|
is LoginViewEvents.OnSignModeSelected -> onSignModeSelected(loginViewEvents)
|
||||||
is LoginViewEvents.OnLoginFlowRetrieved ->
|
is LoginViewEvents.OnLoginFlowRetrieved ->
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
if (loginViewEvents.isSso) {
|
if (loginViewEvents.isSso) {
|
||||||
|
@ -228,18 +228,20 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onServerSelectionDone() = withState(loginViewModel) { state ->
|
private fun onServerSelectionDone(loginViewEvents: LoginViewEvents.OnServerSelectionDone) {
|
||||||
when (state.serverType) {
|
when (loginViewEvents.serverType) {
|
||||||
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
|
ServerType.MatrixOrg -> Unit // In this case, we wait for the login flow
|
||||||
ServerType.Modular,
|
ServerType.Modular,
|
||||||
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
ServerType.Other -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
LoginServerUrlFormFragment::class.java,
|
LoginServerUrlFormFragment::class.java,
|
||||||
option = commonOption)
|
option = commonOption)
|
||||||
|
ServerType.Unknown -> Unit /* Should not happen */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
private fun onSignModeSelected(loginViewEvents: LoginViewEvents.OnSignModeSelected) = withState(loginViewModel) { state ->
|
||||||
when (state.signMode) {
|
// state.signMode could not be ready yet. So use value from the ViewEvent
|
||||||
|
when (loginViewEvents.signMode) {
|
||||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||||
SignMode.SignUp -> {
|
SignMode.SignUp -> {
|
||||||
// This is managed by the LoginViewEvents
|
// This is managed by the LoginViewEvents
|
||||||
|
|
|
@ -54,6 +54,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
|
|
||||||
private var passwordShown = false
|
private var passwordShown = false
|
||||||
private var isSignupMode = false
|
private var isSignupMode = false
|
||||||
|
|
||||||
// Temporary patch for https://github.com/vector-im/riotX-android/issues/1410,
|
// Temporary patch for https://github.com/vector-im/riotX-android/issues/1410,
|
||||||
// waiting for https://github.com/matrix-org/synapse/issues/7576
|
// waiting for https://github.com/matrix-org/synapse/issues/7576
|
||||||
private var isNumericOnlyUserIdForbidden = false
|
private var isNumericOnlyUserIdForbidden = false
|
||||||
|
@ -138,6 +139,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
loginServerIcon.isVisible = false
|
loginServerIcon.isVisible = false
|
||||||
loginTitle.text = getString(R.string.login_signin_matrix_id_title)
|
loginTitle.text = getString(R.string.login_signin_matrix_id_title)
|
||||||
loginNotice.text = getString(R.string.login_signin_matrix_id_notice)
|
loginNotice.text = getString(R.string.login_signin_matrix_id_notice)
|
||||||
|
loginPasswordNotice.isVisible = true
|
||||||
} else {
|
} else {
|
||||||
val resId = when (state.signMode) {
|
val resId = when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
|
@ -164,7 +166,9 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||||
loginNotice.text = getString(R.string.login_server_other_text)
|
loginNotice.text = getString(R.string.login_server_other_text)
|
||||||
}
|
}
|
||||||
|
ServerType.Unknown -> Unit /* Should not happen */
|
||||||
}
|
}
|
||||||
|
loginPasswordNotice.isVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.riotx.features.login
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import butterknife.OnClick
|
import butterknife.OnClick
|
||||||
import com.airbnb.mvrx.withState
|
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.utils.openUrlInChromeCustomTab
|
import im.vector.riotx.core.utils.openUrlInChromeCustomTab
|
||||||
import kotlinx.android.synthetic.main.fragment_login_server_selection.*
|
import kotlinx.android.synthetic.main.fragment_login_server_selection.*
|
||||||
|
@ -40,11 +39,7 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSelectedChoice(state: LoginViewState) {
|
private fun updateSelectedChoice(state: LoginViewState) {
|
||||||
state.serverType.let {
|
loginServerChoiceMatrixOrg.isChecked = state.serverType == ServerType.MatrixOrg
|
||||||
loginServerChoiceMatrixOrg.isChecked = it == ServerType.MatrixOrg
|
|
||||||
loginServerChoiceModular.isChecked = it == ServerType.Modular
|
|
||||||
loginServerChoiceOther.isChecked = it == ServerType.Other
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initTextViews() {
|
private fun initTextViews() {
|
||||||
|
@ -61,42 +56,17 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||||
|
|
||||||
@OnClick(R.id.loginServerChoiceMatrixOrg)
|
@OnClick(R.id.loginServerChoiceMatrixOrg)
|
||||||
fun selectMatrixOrg() {
|
fun selectMatrixOrg() {
|
||||||
if (loginServerChoiceMatrixOrg.isChecked) {
|
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg))
|
||||||
// Consider this is a submit
|
|
||||||
submit()
|
|
||||||
} else {
|
|
||||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.MatrixOrg))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.loginServerChoiceModular)
|
@OnClick(R.id.loginServerChoiceModular)
|
||||||
fun selectModular() {
|
fun selectModular() {
|
||||||
if (loginServerChoiceModular.isChecked) {
|
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular))
|
||||||
// Consider this is a submit
|
|
||||||
submit()
|
|
||||||
} else {
|
|
||||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Modular))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.loginServerChoiceOther)
|
@OnClick(R.id.loginServerChoiceOther)
|
||||||
fun selectOther() {
|
fun selectOther() {
|
||||||
if (loginServerChoiceOther.isChecked) {
|
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other))
|
||||||
// Consider this is a submit
|
|
||||||
submit()
|
|
||||||
} else {
|
|
||||||
loginViewModel.handle(LoginAction.UpdateServerType(ServerType.Other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.loginServerSubmit)
|
|
||||||
fun submit() = withState(loginViewModel) { state ->
|
|
||||||
if (state.serverType == ServerType.MatrixOrg) {
|
|
||||||
// Request login flow here
|
|
||||||
loginViewModel.handle(LoginAction.UpdateHomeServer(getString(R.string.matrix_org_server_url)))
|
|
||||||
} else {
|
|
||||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnServerSelectionDone))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.loginServerIKnowMyIdSubmit)
|
@OnClick(R.id.loginServerIKnowMyIdSubmit)
|
||||||
|
|
|
@ -70,7 +70,7 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||||
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint)
|
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_modular_hint)
|
||||||
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_modular_notice)
|
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_modular_notice)
|
||||||
}
|
}
|
||||||
ServerType.Other -> {
|
else -> {
|
||||||
loginServerUrlFormIcon.isVisible = false
|
loginServerUrlFormIcon.isVisible = false
|
||||||
loginServerUrlFormTitle.text = getString(R.string.login_server_other_title)
|
loginServerUrlFormTitle.text = getString(R.string.login_server_other_title)
|
||||||
loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server)
|
loginServerUrlFormText.text = getString(R.string.login_connect_to_a_custom_server)
|
||||||
|
@ -78,7 +78,6 @@ class LoginServerUrlFormFragment @Inject constructor() : AbstractLoginFragment()
|
||||||
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint)
|
loginServerUrlFormHomeServerUrlTil.hint = getText(R.string.login_server_url_form_other_hint)
|
||||||
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_other_notice)
|
loginServerUrlFormNotice.text = getString(R.string.login_server_url_form_other_notice)
|
||||||
}
|
}
|
||||||
else -> error("This fragment should not be displayed in matrix.org mode")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ open class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractLo
|
||||||
loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
|
loginSignupSigninTitle.text = getString(R.string.login_server_other_title)
|
||||||
loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
|
loginSignupSigninText.text = getString(R.string.login_connect_to, state.homeServerUrl.toReducedUrl())
|
||||||
}
|
}
|
||||||
|
ServerType.Unknown -> Unit /* Should not happen */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,9 @@ sealed class LoginViewEvents : VectorViewEvents {
|
||||||
// Navigation event
|
// Navigation event
|
||||||
|
|
||||||
object OpenServerSelection : LoginViewEvents()
|
object OpenServerSelection : LoginViewEvents()
|
||||||
object OnServerSelectionDone : LoginViewEvents()
|
data class OnServerSelectionDone(val serverType: ServerType) : LoginViewEvents()
|
||||||
data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents()
|
data class OnLoginFlowRetrieved(val isSso: Boolean) : LoginViewEvents()
|
||||||
object OnSignModeSelected : LoginViewEvents()
|
data class OnSignModeSelected(val signMode: SignMode) : LoginViewEvents()
|
||||||
object OnForgetPasswordClicked : LoginViewEvents()
|
object OnForgetPasswordClicked : LoginViewEvents()
|
||||||
object OnResetPasswordSendThreePidDone : LoginViewEvents()
|
object OnResetPasswordSendThreePidDone : LoginViewEvents()
|
||||||
object OnResetPasswordMailConfirmationSuccess : LoginViewEvents()
|
object OnResetPasswordMailConfirmationSuccess : LoginViewEvents()
|
||||||
|
|
|
@ -39,6 +39,7 @@ import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.Stage
|
import im.vector.matrix.android.api.auth.registration.Stage
|
||||||
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
@ -47,6 +48,7 @@ import im.vector.riotx.core.extensions.configureAndStart
|
||||||
import im.vector.riotx.core.extensions.exhaustive
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
import im.vector.riotx.core.resources.StringProvider
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
|
import im.vector.riotx.core.utils.ensureTrailingSlash
|
||||||
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
import im.vector.riotx.features.session.SessionListener
|
import im.vector.riotx.features.session.SessionListener
|
||||||
|
@ -87,8 +89,12 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the last action, to redo it after user has trusted the untrusted certificate
|
||||||
|
private var lastAction: LoginAction? = null
|
||||||
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null
|
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null
|
||||||
|
|
||||||
|
private val matrixOrgUrl = stringProvider.getString(R.string.matrix_org_server_url).ensureTrailingSlash()
|
||||||
|
|
||||||
val currentThreePid: String?
|
val currentThreePid: String?
|
||||||
get() = registrationWizard?.currentThreePid
|
get() = registrationWizard?.currentThreePid
|
||||||
|
|
||||||
|
@ -111,8 +117,8 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
|
is LoginAction.UpdateServerType -> handleUpdateServerType(action)
|
||||||
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
|
is LoginAction.UpdateSignMode -> handleUpdateSignMode(action)
|
||||||
is LoginAction.InitWith -> handleInitWith(action)
|
is LoginAction.InitWith -> handleInitWith(action)
|
||||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
|
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action).also { lastAction = action }
|
||||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action)
|
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
|
||||||
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
|
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
|
||||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||||
|
@ -126,10 +132,23 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
|
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
|
||||||
// It happen when we get the login flow, so alter the homeserver config and retrieve again the login flow
|
// It happen when we get the login flow, or during direct authentication.
|
||||||
currentHomeServerConnectionConfig
|
// So alter the homeserver config and retrieve again the login flow
|
||||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
when (val finalLastAction = lastAction) {
|
||||||
?.let { getLoginFlow(it) }
|
is LoginAction.UpdateHomeServer ->
|
||||||
|
currentHomeServerConnectionConfig
|
||||||
|
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||||
|
?.let { getLoginFlow(it) }
|
||||||
|
is LoginAction.LoginOrRegister ->
|
||||||
|
handleDirectLogin(
|
||||||
|
finalLastAction,
|
||||||
|
HomeServerConnectionConfig.Builder()
|
||||||
|
// Will be replaced by the task
|
||||||
|
.withHomeServerUri("https://dummy.org")
|
||||||
|
.withAllowedFingerPrints(listOf(action.fingerprint))
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
||||||
|
@ -321,7 +340,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
LoginAction.ResetHomeServerType -> {
|
LoginAction.ResetHomeServerType -> {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
serverType = ServerType.MatrixOrg
|
serverType = ServerType.Unknown
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,6 +352,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||||
homeServerUrl = null,
|
homeServerUrl = null,
|
||||||
loginMode = LoginMode.Unknown,
|
loginMode = LoginMode.Unknown,
|
||||||
|
serverType = ServerType.Unknown,
|
||||||
loginModeSupportedTypes = emptyList()
|
loginModeSupportedTypes = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -379,9 +399,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
when (action.signMode) {
|
when (action.signMode) {
|
||||||
SignMode.SignUp -> startRegistrationFlow()
|
SignMode.SignUp -> startRegistrationFlow()
|
||||||
SignMode.SignIn -> startAuthenticationFlow()
|
SignMode.SignIn -> startAuthenticationFlow()
|
||||||
SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected)
|
SignMode.SignInWithMatrixId -> _viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignInWithMatrixId))
|
||||||
SignMode.Unknown -> Unit
|
SignMode.Unknown -> Unit
|
||||||
}.exhaustive
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
|
private fun handleUpdateServerType(action: LoginAction.UpdateServerType) {
|
||||||
|
@ -390,6 +410,15 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
serverType = action.serverType
|
serverType = action.serverType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when (action.serverType) {
|
||||||
|
ServerType.Unknown -> Unit /* Should not happen */
|
||||||
|
ServerType.MatrixOrg ->
|
||||||
|
// Request login flow here
|
||||||
|
handle(LoginAction.UpdateHomeServer(matrixOrgUrl))
|
||||||
|
ServerType.Modular,
|
||||||
|
ServerType.Other -> _viewEvents.post(LoginViewEvents.OnServerSelectionDone(action.serverType))
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInitWith(action: LoginAction.InitWith) {
|
private fun handleInitWith(action: LoginAction.InitWith) {
|
||||||
|
@ -427,7 +456,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// TODO Handled JobCancellationException
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncResetPassword = Fail(failure)
|
asyncResetPassword = Fail(failure)
|
||||||
|
@ -469,7 +497,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// TODO Handled JobCancellationException
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncResetMailConfirmed = Fail(failure)
|
asyncResetMailConfirmed = Fail(failure)
|
||||||
|
@ -485,23 +512,22 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||||
SignMode.SignIn -> handleLogin(action)
|
SignMode.SignIn -> handleLogin(action)
|
||||||
SignMode.SignUp -> handleRegisterWith(action)
|
SignMode.SignUp -> handleRegisterWith(action)
|
||||||
SignMode.SignInWithMatrixId -> handleDirectLogin(action)
|
SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDirectLogin(action: LoginAction.LoginOrRegister) {
|
private fun handleDirectLogin(action: LoginAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncLoginAction = Loading()
|
asyncLoginAction = Loading()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Handle certificate error in this case. Direct login is deactivated now, so we will handle that later
|
authenticationService.getWellKnownData(action.username, homeServerConnectionConfig, object : MatrixCallback<WellknownResult> {
|
||||||
authenticationService.getWellKnownData(action.username, null, object : MatrixCallback<WellknownResult> {
|
|
||||||
override fun onSuccess(data: WellknownResult) {
|
override fun onSuccess(data: WellknownResult) {
|
||||||
when (data) {
|
when (data) {
|
||||||
is WellknownResult.Prompt ->
|
is WellknownResult.Prompt ->
|
||||||
onWellknownSuccess(action, data)
|
onWellknownSuccess(action, data, homeServerConnectionConfig)
|
||||||
is WellknownResult.InvalidMatrixId -> {
|
is WellknownResult.InvalidMatrixId -> {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -522,23 +548,26 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
setState {
|
onDirectLoginError(failure)
|
||||||
copy(
|
|
||||||
asyncLoginAction = Fail(failure)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) {
|
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister,
|
||||||
val homeServerConnectionConfig = HomeServerConnectionConfig(
|
wellKnownPrompt: WellknownResult.Prompt,
|
||||||
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
homeServerConnectionConfig: HomeServerConnectionConfig?) {
|
||||||
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
val alteredHomeServerConnectionConfig = homeServerConnectionConfig
|
||||||
)
|
?.copy(
|
||||||
|
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||||
|
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||||
|
)
|
||||||
|
?: HomeServerConnectionConfig(
|
||||||
|
homeServerUri = Uri.parse(wellKnownPrompt.homeServerUrl),
|
||||||
|
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||||
|
)
|
||||||
|
|
||||||
authenticationService.directAuthentication(
|
authenticationService.directAuthentication(
|
||||||
homeServerConnectionConfig,
|
alteredHomeServerConnectionConfig,
|
||||||
action.username,
|
action.username,
|
||||||
action.password,
|
action.password,
|
||||||
action.initialDeviceName,
|
action.initialDeviceName,
|
||||||
|
@ -548,15 +577,29 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
setState {
|
onDirectLoginError(failure)
|
||||||
copy(
|
|
||||||
asyncLoginAction = Fail(failure)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onDirectLoginError(failure: Throwable) {
|
||||||
|
if (failure is Failure.UnrecognizedCertificateFailure) {
|
||||||
|
// Display this error in a dialog
|
||||||
|
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
||||||
val safeLoginWizard = loginWizard
|
val safeLoginWizard = loginWizard
|
||||||
|
|
||||||
|
@ -584,7 +627,6 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
override fun onFailure(failure: Throwable) {
|
||||||
// TODO Handled JobCancellationException
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncLoginAction = Fail(failure)
|
asyncLoginAction = Fail(failure)
|
||||||
|
@ -609,7 +651,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
// Ensure Wizard is ready
|
// Ensure Wizard is ready
|
||||||
loginWizard
|
loginWizard
|
||||||
|
|
||||||
_viewEvents.post(LoginViewEvents.OnSignModeSelected)
|
_viewEvents.post(LoginViewEvents.OnSignModeSelected(SignMode.SignIn))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFlowResponse(flowResult: FlowResult) {
|
private fun onFlowResponse(flowResult: FlowResult) {
|
||||||
|
@ -673,7 +715,10 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncHomeServerLoginFlowRequest = Loading()
|
asyncHomeServerLoginFlowRequest = Loading(),
|
||||||
|
// If user has entered https://matrix.org, ensure that server type is ServerType.MatrixOrg
|
||||||
|
// It is also useful to set the value again in the case of a certificate error on matrix.org
|
||||||
|
serverType = if (homeServerConnectionConfig.homeServerUri.toString() == matrixOrgUrl) ServerType.MatrixOrg else serverType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -682,7 +727,9 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||||
|
// If we were trying to retrieve matrix.org login flow, also reset the serverType
|
||||||
|
serverType = if (serverType == ServerType.MatrixOrg) ServerType.Unknown else serverType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ data class LoginViewState(
|
||||||
|
|
||||||
// User choices
|
// User choices
|
||||||
@PersistState
|
@PersistState
|
||||||
val serverType: ServerType = ServerType.MatrixOrg,
|
val serverType: ServerType = ServerType.Unknown,
|
||||||
@PersistState
|
@PersistState
|
||||||
val signMode: SignMode = SignMode.Unknown,
|
val signMode: SignMode = SignMode.Unknown,
|
||||||
@PersistState
|
@PersistState
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.riotx.features.login
|
package im.vector.riotx.features.login
|
||||||
|
|
||||||
enum class ServerType {
|
enum class ServerType {
|
||||||
|
Unknown,
|
||||||
MatrixOrg,
|
MatrixOrg,
|
||||||
Modular,
|
Modular,
|
||||||
Other
|
Other
|
||||||
|
|
|
@ -205,7 +205,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
|
fun buildForegroundServiceNotification(@StringRes subTitleResId: Int, withProgress: Boolean = true): Notification {
|
||||||
// build the pending intent go to the home screen if this is clicked.
|
// build the pending intent go to the home screen if this is clicked.
|
||||||
val i = Intent(context, HomeActivity::class.java)
|
val i = HomeActivity.newIntent(context)
|
||||||
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
i.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
val pi = PendingIntent.getActivity(context, 0, i, 0)
|
val pi = PendingIntent.getActivity(context, 0, i, 0)
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
|
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
|
||||||
|
|
||||||
val answerCallPendingIntent = TaskStackBuilder.create(context)
|
val answerCallPendingIntent = TaskStackBuilder.create(context)
|
||||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
|
||||||
.addNextIntent(VectorCallActivity.newIntent(
|
.addNextIntent(VectorCallActivity.newIntent(
|
||||||
context = context,
|
context = context,
|
||||||
callId = callId,
|
callId = callId,
|
||||||
|
@ -459,7 +459,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
)
|
)
|
||||||
|
|
||||||
val contentPendingIntent = TaskStackBuilder.create(context)
|
val contentPendingIntent = TaskStackBuilder.create(context)
|
||||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
|
||||||
// TODO other userId
|
// TODO other userId
|
||||||
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
|
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
|
||||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
@ -651,7 +651,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
stringProvider.getString(R.string.join),
|
stringProvider.getString(R.string.join),
|
||||||
joinIntentPendingIntent)
|
joinIntentPendingIntent)
|
||||||
|
|
||||||
val contentIntent = Intent(context, HomeActivity::class.java)
|
val contentIntent = HomeActivity.newIntent(context)
|
||||||
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
||||||
contentIntent.data = Uri.parse("foobar://" + inviteNotifiableEvent.eventId)
|
contentIntent.data = Uri.parse("foobar://" + inviteNotifiableEvent.eventId)
|
||||||
|
@ -689,7 +689,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
.setColor(accentColor)
|
.setColor(accentColor)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.apply {
|
.apply {
|
||||||
val contentIntent = Intent(context, HomeActivity::class.java)
|
val contentIntent = HomeActivity.newIntent(context)
|
||||||
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
contentIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
// pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that
|
||||||
contentIntent.data = Uri.parse("foobar://" + simpleNotifiableEvent.eventId)
|
contentIntent.data = Uri.parse("foobar://" + simpleNotifiableEvent.eventId)
|
||||||
|
@ -718,7 +718,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||||
|
|
||||||
// Recreate the back stack
|
// Recreate the back stack
|
||||||
return TaskStackBuilder.create(context)
|
return TaskStackBuilder.create(context)
|
||||||
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
|
.addNextIntentWithParentStack(HomeActivity.newIntent(context))
|
||||||
.addNextIntent(roomIntentTap)
|
.addNextIntent(roomIntentTap)
|
||||||
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,8 +93,9 @@ class SoftLogoutFragment @Inject constructor(
|
||||||
softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password))
|
softLogoutViewModel.handle(SoftLogoutAction.SignInAgain(password))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun signinFallbackSubmit() {
|
override fun signinFallbackSubmit() = withState(loginViewModel) { state ->
|
||||||
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected))
|
// The loginViewModel has been prepared for a SSO/login fallback recovery (above)
|
||||||
|
loginViewModel.handle(LoginAction.PostViewEvent(LoginViewEvents.OnSignModeSelected(state.signMode)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearData() {
|
override fun clearData() {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item android:drawable="@drawable/bg_login_server_checked" android:state_checked="true" />
|
<item android:drawable="@drawable/bg_login_server_checked" android:state_checked="true" />
|
||||||
|
<item android:drawable="@drawable/bg_login_server_checked" android:state_pressed="true" />
|
||||||
|
|
||||||
<item android:drawable="@drawable/bg_login_server" />
|
<item android:drawable="@drawable/bg_login_server" />
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,16 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginPasswordNotice"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text="@string/login_signin_matrix_id_password_notice"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginServerTitle" />
|
app:layout_constraintTop_toBottomOf="@+id/loginServerTitle" />
|
||||||
|
|
||||||
|
<!-- Use a CheckableConstraintLayout to keep the pressed state when retrieving login flow -->
|
||||||
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
||||||
android:id="@+id/loginServerChoiceMatrixOrg"
|
android:id="@+id/loginServerChoiceMatrixOrg"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
|
|
||||||
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
||||||
|
|
||||||
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/loginServerChoiceModular"
|
android:id="@+id/loginServerChoiceModular"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -135,9 +136,9 @@
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/loginServerChoiceModularText" />
|
app:layout_constraintTop_toTopOf="@+id/loginServerChoiceModularText" />
|
||||||
|
|
||||||
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<im.vector.riotx.core.platform.CheckableConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/loginServerChoiceOther"
|
android:id="@+id/loginServerChoiceOther"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -178,45 +179,20 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOtherTitle" />
|
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOtherTitle" />
|
||||||
|
|
||||||
</im.vector.riotx.core.platform.CheckableConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/loginServerSubmit"
|
|
||||||
style="@style/Style.Vector.Login.Button"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:text="@string/login_continue"
|
|
||||||
android:transitionName="loginSubmitTransition"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/loginServerIKnowMyIdNotice"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/loginServerIKnowMyIdNotice"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="@string/login_connect_using_matrix_id_notice"
|
|
||||||
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginServerSubmit" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/loginServerIKnowMyIdSubmit"
|
android:id="@+id/loginServerIKnowMyIdSubmit"
|
||||||
style="@style/Style.Vector.Login.Button.Text"
|
style="@style/Style.Vector.Login.Button.Text"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
android:text="@string/login_connect_using_matrix_id_submit"
|
android:text="@string/login_connect_using_matrix_id_submit"
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginServerIKnowMyIdNotice" />
|
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
|
|
@ -1995,10 +1995,11 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
|
||||||
</plurals>
|
</plurals>
|
||||||
|
|
||||||
<string name="login_connect_using_matrix_id_notice">Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method:</string>
|
<string name="login_connect_using_matrix_id_notice">Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method:</string>
|
||||||
<string name="login_connect_using_matrix_id_submit">Sign in with my Matrix identifier</string>
|
<string name="login_connect_using_matrix_id_submit">Sign in with Matrix ID</string>
|
||||||
<string name="login_signin_matrix_id_title">Sign in</string>
|
<string name="login_signin_matrix_id_title">Sign in with Matrix ID</string>
|
||||||
<string name="login_signin_matrix_id_notice">Enter your identifier and your password</string>
|
<string name="login_signin_matrix_id_notice">If you set up an account on a homeserver, use your Matrix ID (e.g. @user:domain.com) and password below.</string>
|
||||||
<string name="login_signin_matrix_id_hint">User identifier</string>
|
<string name="login_signin_matrix_id_hint">Matrix ID</string>
|
||||||
|
<string name="login_signin_matrix_id_password_notice">If you don’t know your password, go back to reset it.</string>
|
||||||
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
|
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
|
||||||
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
|
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue